Skip to content

Commit

Permalink
[ODS-6546] Perform joins to authorization views on base tables rather…
Browse files Browse the repository at this point in the history
… than derived tables to take advantage of indexes (#1177)
  • Loading branch information
gmcelhanon authored Nov 11, 2024
1 parent 8aa271f commit f49da47
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,6 +23,7 @@ public static class QueryBuilderExtensions
/// Applies a join-based filter to the criteria for the specified authorization view.
/// </summary>
/// <param name="queryBuilder">The <see cref="QueryBuilder" /> to which criteria should be applied.</param>
/// <param name="resource"></param>
/// <param name="parameters">The named parameters to be used to satisfy additional filtering requirements.</param>
/// <param name="viewName">The name of the view to be filtered.</param>
/// <param name="subjectEndpointName">The name of the property to be joined for the entity being queried.</param>
Expand All @@ -30,6 +33,7 @@ public static class QueryBuilderExtensions
/// <param name="authViewAlias">The name of the property to be used for auth View Alias name.</param>
public static void ApplySingleColumnJoinFilter(
this QueryBuilder queryBuilder,
Resource resource,
IDictionary<string, object> parameters,
string viewName,
string subjectEndpointName,
Expand All @@ -43,18 +47,19 @@ 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);
}

private static void ApplySingleColumnJoinFilterUsingCtes(
this QueryBuilder queryBuilder,
Resource resource,
IDictionary<string, object> parameters,
string viewName,
string subjectEndpointName,
Expand Down Expand Up @@ -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
Expand All @@ -113,6 +120,7 @@ private static void ApplySingleColumnJoinFilterUsingCtes(

private static void ApplySingleColumnJoinFilterUsingJoins(
QueryBuilder queryBuilder,
Resource resource,
IDictionary<string, object> parameters,
string viewName,
string subjectEndpointName,
Expand All @@ -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
{
Expand Down Expand Up @@ -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);
}


/// <summary>
/// Applies a join-based filter to the <see cref="QueryBuilder" /> for the specified custom authorization view.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public ViewBasedAuthorizationFilterDefinition(
}

@where.ApplySingleColumnJoinFilter(
resource,
parameters,
viewName,
subjectEndpointNames[0],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ protected IEnumerable<ViewBasedAuthorizationFilterDefinition> CreateAllEducation
.OrderBy(p => p)
.Select(personType => $"{personType}USI")
.ToArray();

return personUsiNames.Select(usiName =>
new ViewBasedAuthorizationFilterDefinition(
$"{RelationshipAuthorizationConventions.FilterNamePrefix}To{usiName}{authorizationPathModifier}",
Expand Down
20 changes: 20 additions & 0 deletions Application/EdFi.Ods.Common/Providers/Queries/AliasConstants.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The alias used when building queries that access an aggregate root's base table.
/// </summary>
public const string BaseAlias = "b";

/// <summary>
/// The alias used when building queries that access an aggregate root table (which is the derived table in an
/// inheritance relationship, when applicable).
/// </summary>
public const string RootAlias = "r";
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ namespace EdFi.Ods.Common.Providers.Queries;

public static class EntityExtensions
{
private const string BaseAlias = "b";
private const string StandardAlias = "r";

/// <summary>
/// Determines the appropriate table alias for the given aggregate root entity.
/// </summary>
/// <param name="aggregateRootEntity">The aggregate root entity to determine the alias for.</param>
/// <returns>
/// Returns "b" if the entity is derived, otherwise returns "r".
/// Returns <see cref="AliasConstants.BaseAlias"/> if the entity is derived, otherwise returns <see cref="AliasConstants.RootAlias"/>.
/// </returns>
public static string RootTableAlias(this Entity aggregateRootEntity)
{
return aggregateRootEntity.IsDerived ? BaseAlias : StandardAlias;
return aggregateRootEntity.IsDerived ? AliasConstants.BaseAlias : AliasConstants.RootAlias;
}
}

0 comments on commit f49da47

Please sign in to comment.