diff --git a/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/QueryBuilderExtensions.cs b/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/QueryBuilderExtensions.cs index 833ea4e4cb..c0a022f75a 100644 --- a/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/QueryBuilderExtensions.cs +++ b/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/QueryBuilderExtensions.cs @@ -8,6 +8,8 @@ using System.Linq; using EdFi.Ods.Common.Context; using EdFi.Ods.Common.Database.Querying; +using EdFi.Ods.Common.Models.Resource; +using EdFi.Ods.Common.Providers.Queries; using EdFi.Ods.Common.Security.Authorization; using EdFi.Ods.Common.Security.CustomViewBased; @@ -21,6 +23,7 @@ public static class QueryBuilderExtensions /// Applies a join-based filter to the criteria for the specified authorization view. /// /// The to which criteria should be applied. + /// /// The named parameters to be used to satisfy additional filtering requirements. /// The name of the view to be filtered. /// The name of the property to be joined for the entity being queried. @@ -30,6 +33,7 @@ public static class QueryBuilderExtensions /// The name of the property to be used for auth View Alias name. public static void ApplySingleColumnJoinFilter( this QueryBuilder queryBuilder, + Resource resource, IDictionary parameters, string viewName, string subjectEndpointName, @@ -43,11 +47,11 @@ public static void ApplySingleColumnJoinFilter( if (useJoinAuth) { - ApplySingleColumnJoinFilterUsingJoins(queryBuilder, parameters, viewName, subjectEndpointName, viewSourceEndpointName, viewTargetEndpointName, joinType, authViewAlias); + ApplySingleColumnJoinFilterUsingJoins(queryBuilder, resource, parameters, viewName, subjectEndpointName, viewSourceEndpointName, viewTargetEndpointName, joinType, authViewAlias); return; } - ApplySingleColumnJoinFilterUsingCtes(queryBuilder, parameters, viewName, subjectEndpointName, viewSourceEndpointName, + ApplySingleColumnJoinFilterUsingCtes(queryBuilder, resource, parameters, viewName, subjectEndpointName, viewSourceEndpointName, viewTargetEndpointName, joinType, authViewAlias); @@ -55,6 +59,7 @@ public static void ApplySingleColumnJoinFilter( private static void ApplySingleColumnJoinFilterUsingCtes( this QueryBuilder queryBuilder, + Resource resource, IDictionary parameters, string viewName, string subjectEndpointName, @@ -90,19 +95,21 @@ private static void ApplySingleColumnJoinFilterUsingCtes( // Add the CTE to the main query, with alias queryBuilder.With(authViewAlias, cte); + var subjectJoin = GetSubjectJoinDetails(resource, subjectEndpointName); + // Apply join to the authorization CTE if (joinType == JoinType.InnerJoin) { queryBuilder.Join( authViewAlias, - j => j.On($"r.{subjectEndpointName}", $"{authViewAlias}.{viewTargetEndpointName}")); + j => j.On($"{subjectJoin.tableAlias}.{subjectJoin.endpointName}", $"{authViewAlias}.{viewTargetEndpointName}")); } else if (joinType == JoinType.LeftOuterJoin) { queryBuilder.LeftJoin( authViewAlias, - j => j.On($"r.{subjectEndpointName}", $"{authViewAlias}.{viewTargetEndpointName}")); - + j => j.On($"{subjectJoin.tableAlias}.{subjectJoin.endpointName}", $"{authViewAlias}.{viewTargetEndpointName}")); + queryBuilder.Where(qb => qb.WhereNotNull($"{authViewAlias}.{viewTargetEndpointName}")); } else @@ -113,6 +120,7 @@ private static void ApplySingleColumnJoinFilterUsingCtes( private static void ApplySingleColumnJoinFilterUsingJoins( QueryBuilder queryBuilder, + Resource resource, IDictionary parameters, string viewName, string subjectEndpointName, @@ -129,18 +137,20 @@ private static void ApplySingleColumnJoinFilterUsingJoins( authViewAlias = string.IsNullOrWhiteSpace(authViewAlias) ? $"authView{viewName}" : $"authView{authViewAlias}"; - // Apply authorization join using ICriteria + var subjectJoin = GetSubjectJoinDetails(resource, subjectEndpointName); + + // Apply join to the authorization view if (joinType == JoinType.InnerJoin) { queryBuilder.Join( $"auth.{viewName} AS {authViewAlias}", - j => j.On($"r.{subjectEndpointName}", $"{authViewAlias}.{viewTargetEndpointName}")); + j => j.On($"{subjectJoin.tableAlias}.{subjectJoin.tableAlias}", $"{authViewAlias}.{viewTargetEndpointName}")); } else if (joinType == JoinType.LeftOuterJoin) { queryBuilder.LeftJoin( $"auth.{viewName} AS {authViewAlias}", - j => j.On($"r.{subjectEndpointName}", $"{authViewAlias}.{viewTargetEndpointName}")); + j => j.On($"{subjectJoin.tableAlias}.{subjectJoin.endpointName}", $"{authViewAlias}.{viewTargetEndpointName}")); } else { @@ -175,6 +185,21 @@ private static void ApplySingleColumnJoinFilterUsingJoins( } } + private static (string tableAlias, string endpointName) GetSubjectJoinDetails(Resource resource, string subjectEndpointName) + { + // If the resource is derived and the subject property is identifying (part of the PK), join using the base table + // to take advantage of indexes that are built on the base table and not each of the derived tables. + if (resource.Entity.IsDerived + && resource.Entity.PropertyByName.TryGetValue(subjectEndpointName, out var subjectProperty) + && subjectProperty.IsIdentifying) + { + return (AliasConstants.BaseAlias, subjectProperty.BaseProperty.PropertyName); + } + + return (AliasConstants.RootAlias, subjectEndpointName); + } + + /// /// Applies a join-based filter to the for the specified custom authorization view. /// diff --git a/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/ViewBasedAuthorizationFilterDefinition.cs b/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/ViewBasedAuthorizationFilterDefinition.cs index b60fe0c655..2ffe326303 100644 --- a/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/ViewBasedAuthorizationFilterDefinition.cs +++ b/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/Filters/ViewBasedAuthorizationFilterDefinition.cs @@ -52,6 +52,7 @@ public ViewBasedAuthorizationFilterDefinition( } @where.ApplySingleColumnJoinFilter( + resource, parameters, viewName, subjectEndpointNames[0], diff --git a/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/RelationshipsAuthorizationStrategyFilterDefinitionsFactory.cs b/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/RelationshipsAuthorizationStrategyFilterDefinitionsFactory.cs index 25bc5570d8..a4f863acf2 100644 --- a/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/RelationshipsAuthorizationStrategyFilterDefinitionsFactory.cs +++ b/Application/EdFi.Ods.Api/Security/AuthorizationStrategies/Relationships/RelationshipsAuthorizationStrategyFilterDefinitionsFactory.cs @@ -70,7 +70,7 @@ protected IEnumerable CreateAllEducation .OrderBy(p => p) .Select(personType => $"{personType}USI") .ToArray(); - + return personUsiNames.Select(usiName => new ViewBasedAuthorizationFilterDefinition( $"{RelationshipAuthorizationConventions.FilterNamePrefix}To{usiName}{authorizationPathModifier}", diff --git a/Application/EdFi.Ods.Common/Providers/Queries/AliasConstants.cs b/Application/EdFi.Ods.Common/Providers/Queries/AliasConstants.cs new file mode 100644 index 0000000000..7c0f6c934d --- /dev/null +++ b/Application/EdFi.Ods.Common/Providers/Queries/AliasConstants.cs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// Licensed to the Ed-Fi Alliance under one or more agreements. +// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. +// See the LICENSE and NOTICES files in the project root for more information. + +namespace EdFi.Ods.Common.Providers.Queries; + +public class AliasConstants +{ + /// + /// The alias used when building queries that access an aggregate root's base table. + /// + public const string BaseAlias = "b"; + + /// + /// The alias used when building queries that access an aggregate root table (which is the derived table in an + /// inheritance relationship, when applicable). + /// + public const string RootAlias = "r"; +} diff --git a/Application/EdFi.Ods.Common/Providers/Queries/EntityExtensions.cs b/Application/EdFi.Ods.Common/Providers/Queries/EntityExtensions.cs index e4a03642ce..8c0bbc515f 100644 --- a/Application/EdFi.Ods.Common/Providers/Queries/EntityExtensions.cs +++ b/Application/EdFi.Ods.Common/Providers/Queries/EntityExtensions.cs @@ -9,18 +9,15 @@ namespace EdFi.Ods.Common.Providers.Queries; public static class EntityExtensions { - private const string BaseAlias = "b"; - private const string StandardAlias = "r"; - /// /// Determines the appropriate table alias for the given aggregate root entity. /// /// The aggregate root entity to determine the alias for. /// - /// Returns "b" if the entity is derived, otherwise returns "r". + /// Returns if the entity is derived, otherwise returns . /// public static string RootTableAlias(this Entity aggregateRootEntity) { - return aggregateRootEntity.IsDerived ? BaseAlias : StandardAlias; + return aggregateRootEntity.IsDerived ? AliasConstants.BaseAlias : AliasConstants.RootAlias; } }