Skip to content

Reduce complexity of QueryLayer after composition #1735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/JsonApiDotNetCore/Queries/QueryLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace JsonApiDotNetCore.Queries;
[PublicAPI]
public sealed class QueryLayer
{
internal bool IsEmpty => Filter == null && Sort == null && Pagination?.PageSize == null && (Selection == null || Selection.IsEmpty);

public ResourceType ResourceType { get; }

public IncludeExpression? Include { get; set; }
Expand Down
59 changes: 45 additions & 14 deletions src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,20 @@ private QueryLayer ComposeTopLayer(ImmutableArray<ExpressionInScope> constraints
_paginationContext.PageSize = topPagination.PageSize;
_paginationContext.PageNumber = topPagination.PageNumber;

return new QueryLayer(resourceType)
var topLayer = new QueryLayer(resourceType)
{
Filter = GetFilter(expressionsInTopScope, resourceType),
Sort = GetSort(expressionsInTopScope, resourceType),
Pagination = topPagination,
Selection = GetSelectionForSparseAttributeSet(resourceType)
};

if (topLayer is { Pagination.PageSize: not null, Sort: null })
{
topLayer.Sort = CreateSortById(resourceType);
}

return topLayer;
}

private IncludeExpression ComposeChildren(QueryLayer topLayer, ImmutableArray<ExpressionInScope> constraints)
Expand Down Expand Up @@ -237,17 +244,22 @@ private IImmutableSet<IncludeElementExpression> ProcessIncludeSet(IImmutableSet<
ResourceType resourceType = includeElement.Relationship.RightType;
bool isToManyRelationship = includeElement.Relationship is HasManyAttribute;

var child = new QueryLayer(resourceType)
var subLayer = new QueryLayer(resourceType)
{
Filter = isToManyRelationship ? GetFilter(expressionsInCurrentScope, resourceType) : null,
Sort = isToManyRelationship ? GetSort(expressionsInCurrentScope, resourceType) : null,
Pagination = isToManyRelationship ? GetPagination(expressionsInCurrentScope, resourceType) : null,
Selection = GetSelectionForSparseAttributeSet(resourceType)
};

selectors.IncludeRelationship(includeElement.Relationship, child);
if (subLayer is { Pagination.PageSize: not null, Sort: null })
{
subLayer.Sort = CreateSortById(resourceType);
}

IImmutableSet<IncludeElementExpression> updatedChildren = ProcessIncludeSet(includeElement.Children, child, relationshipChain, constraints);
selectors.IncludeRelationship(includeElement.Relationship, subLayer);

IImmutableSet<IncludeElementExpression> updatedChildren = ProcessIncludeSet(includeElement.Children, subLayer, relationshipChain, constraints);

if (!ReferenceEquals(includeElement.Children, updatedChildren))
{
Expand All @@ -256,9 +268,30 @@ private IImmutableSet<IncludeElementExpression> ProcessIncludeSet(IImmutableSet<
}
}

EliminateRedundantSelectors(parentLayer);

return updatesInChildren.Count == 0 ? includeElementsEvaluated : ApplyIncludeElementUpdates(includeElementsEvaluated, updatesInChildren);
}

private static void EliminateRedundantSelectors(QueryLayer parentLayer)
{
if (parentLayer.Selection != null)
{
foreach ((ResourceType resourceType, FieldSelectors selectors) in parentLayer.Selection.ToArray())
{
if (selectors.ContainsOnlyRelationships && selectors.Values.OfType<QueryLayer>().All(subLayer => subLayer.IsEmpty))
{
parentLayer.Selection.Remove(resourceType);
}
}

if (parentLayer.Selection.IsEmpty)
{
parentLayer.Selection = null;
}
}
}

private static ImmutableHashSet<IncludeElementExpression> ApplyIncludeElementUpdates(IImmutableSet<IncludeElementExpression> includeElements,
Dictionary<IncludeElementExpression, IImmutableSet<IncludeElementExpression>> updatesInChildren)
{
Expand Down Expand Up @@ -507,23 +540,21 @@ protected virtual IImmutableSet<IncludeElementExpression> GetIncludeElements(IIm
return _resourceDefinitionAccessor.OnApplyFilter(resourceType, filter);
}

protected virtual SortExpression GetSort(IReadOnlyCollection<QueryExpression> expressionsInScope, ResourceType resourceType)
protected virtual SortExpression? GetSort(IReadOnlyCollection<QueryExpression> expressionsInScope, ResourceType resourceType)
{
ArgumentNullException.ThrowIfNull(expressionsInScope);
ArgumentNullException.ThrowIfNull(resourceType);

SortExpression? sort = expressionsInScope.OfType<SortExpression>().FirstOrDefault();

sort = _resourceDefinitionAccessor.OnApplySort(resourceType, sort);

if (sort == null)
{
AttrAttribute idAttribute = GetIdAttribute(resourceType);
var idAscendingSort = new SortElementExpression(new ResourceFieldChainExpression(idAttribute), true);
sort = new SortExpression(ImmutableArray.Create(idAscendingSort));
}
return _resourceDefinitionAccessor.OnApplySort(resourceType, sort);
}

return sort;
private SortExpression CreateSortById(ResourceType resourceType)
{
AttrAttribute idAttribute = GetIdAttribute(resourceType);
var idAscendingSort = new SortElementExpression(new ResourceFieldChainExpression(idAttribute), true);
return new SortExpression(ImmutableArray.Create(idAscendingSort));
}

protected virtual PaginationExpression GetPagination(IReadOnlyCollection<QueryExpression> expressionsInScope, ResourceType resourceType)
Expand Down
5 changes: 0 additions & 5 deletions test/DapperTests/IntegrationTests/QueryStrings/FilterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ SELECT COUNT(*)
FROM "Tags" AS t1
LEFT JOIN "RgbColors" AS t2 ON t1."Id" = t2."TagId"
WHERE t2."Id" = @p1
ORDER BY t1."Id"
"""));

command.Parameters.Should().HaveCount(1);
Expand Down Expand Up @@ -144,7 +143,6 @@ SELECT COUNT(*)
FROM "Tags" AS t1
LEFT JOIN "RgbColors" AS t2 ON t1."Id" = t2."TagId"
WHERE t2."Id" IN (@p1, @p2)
ORDER BY t1."Id"
"""));

command.Parameters.Should().HaveCount(2);
Expand Down Expand Up @@ -662,7 +660,6 @@ SELECT COUNT(*)
SELECT t1."Id", t1."FirstName", t1."LastName"
FROM "People" AS t1
WHERE (NOT (t1."FirstName" = @p1)) OR (t1."FirstName" IS NULL)
ORDER BY t1."Id"
"""));

command.Parameters.Should().HaveCount(1);
Expand Down Expand Up @@ -867,7 +864,6 @@ SELECT COUNT(*)
SELECT t1."Id", t1."Name"
FROM "Tags" AS t1
WHERE (t1."Name" LIKE '%A\%%' ESCAPE '\') OR (t1."Name" LIKE '%A\_%' ESCAPE '\') OR (t1."Name" LIKE '%A\\%' ESCAPE '\') OR (t1."Name" LIKE '%A''%') OR (t1."Name" LIKE '%\%\_\\''%' ESCAPE '\')
ORDER BY t1."Id"
"""));

command.Parameters.Should().BeEmpty();
Expand Down Expand Up @@ -1177,7 +1173,6 @@ SELECT 1
LEFT JOIN "People" AS t3 ON t2."AssigneeId" = t3."Id"
WHERE (t1."Id" = t2."OwnerId") AND (NOT (t3."Id" IS NULL)) AND (t3."FirstName" IS NULL)
)
ORDER BY t1."Id"
"""));

command.Parameters.Should().BeEmpty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ SELECT COUNT(*)
INNER JOIN "People" AS t3 ON t1."OwnerId" = t3."Id"
LEFT JOIN "TodoItems" AS t4 ON t3."Id" = t4."AssigneeId"
LEFT JOIN "Tags" AS t5 ON t1."Id" = t5."TodoItemId"
ORDER BY t1."Priority", t1."LastModifiedAt" DESC, t4."Priority", t4."LastModifiedAt" DESC, t5."Id"
ORDER BY t1."Priority", t1."LastModifiedAt" DESC, t4."Priority", t4."LastModifiedAt" DESC
"""));

command.Parameters.Should().BeEmpty();
Expand Down Expand Up @@ -231,7 +231,7 @@ SELECT COUNT(*)
FROM "TodoItems" AS t1
LEFT JOIN "Tags" AS t2 ON t1."Id" = t2."TodoItemId"
LEFT JOIN "RgbColors" AS t3 ON t2."Id" = t3."TagId"
ORDER BY t1."Priority", t1."LastModifiedAt" DESC, t2."Id"
ORDER BY t1."Priority", t1."LastModifiedAt" DESC
"""));

command.Parameters.Should().BeEmpty();
Expand Down
4 changes: 2 additions & 2 deletions test/DapperTests/IntegrationTests/QueryStrings/SortTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ ORDER BY (
SELECT COUNT(*)
FROM "Tags" AS t3
WHERE t2."Id" = t3."TodoItemId"
) DESC, t2."Id", t4."Id"
) DESC, t2."Id"
"""));

command.Parameters.Should().HaveCount(1);
Expand Down Expand Up @@ -415,7 +415,7 @@ SELECT COUNT(*)
SELECT t1."Id", t1."FirstName", t1."LastName", t2."Id", t2."CreatedAt", t2."Description", t2."DurationInHours", t2."LastModifiedAt", t2."Priority"
FROM "People" AS t1
LEFT JOIN "TodoItems" AS t2 ON t1."Id" = t2."OwnerId"
ORDER BY t1."Id", (
ORDER BY (
SELECT COUNT(*)
FROM "Tags" AS t3
WHERE t2."Id" = t3."TodoItemId"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ SELECT COUNT(*)
FROM "TodoItems" AS t1
LEFT JOIN "Tags" AS t2 ON t1."Id" = t2."TodoItemId"
WHERE t1."Id" = @p1
ORDER BY t2."Id"
"""));

command.Parameters.Should().HaveCount(1);
Expand Down Expand Up @@ -400,7 +399,6 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
FROM "TodoItems" AS t1
LEFT JOIN "Tags" AS t2 ON t1."Id" = t2."TodoItemId"
WHERE t1."Id" = @p1
ORDER BY t2."Id"
"""));

command.Parameters.Should().HaveCount(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ SELECT COUNT(*)
FROM "TodoItems" AS t1
LEFT JOIN "Tags" AS t2 ON t1."Id" = t2."TodoItemId"
WHERE t1."Id" = @p1
ORDER BY t2."Id"
"""));

command.Parameters.Should().HaveCount(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ SELECT COUNT(*)
FROM "TodoItems" AS t1
LEFT JOIN "Tags" AS t2 ON t1."Id" = t2."TodoItemId"
WHERE t1."Id" = @p1
ORDER BY t2."Id"
"""));

command.Parameters.Should().HaveCount(1);
Expand Down
23 changes: 11 additions & 12 deletions test/DapperTests/IntegrationTests/Sql/SubQueryInJoinTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ SELECT COUNT(*)
SELECT t1."Id", t1."FirstName", t1."LastName", t2."Id", t2."LastUsedAt", t2."UserName"
FROM "People" AS t1
LEFT JOIN "LoginAccounts" AS t2 ON t1."AccountId" = t2."Id"
ORDER BY t1."Id"
"""));

command.Parameters.Should().BeEmpty();
Expand Down Expand Up @@ -111,7 +110,7 @@ SELECT COUNT(*)
SELECT t1."Id", t1."FirstName", t1."LastName", t2."Id", t2."CreatedAt", t2."Description", t2."DurationInHours", t2."LastModifiedAt", t2."Priority"
FROM "People" AS t1
LEFT JOIN "TodoItems" AS t2 ON t1."Id" = t2."OwnerId"
ORDER BY t1."Id", t2."Priority", t2."LastModifiedAt" DESC
ORDER BY t2."Priority", t2."LastModifiedAt" DESC
"""));

command.Parameters.Should().BeEmpty();
Expand Down Expand Up @@ -160,7 +159,7 @@ SELECT COUNT(*)
SELECT t1."Id", t1."FirstName", t1."LastName", t2."Id", t2."CreatedAt", t2."Description", t2."DurationInHours", t2."LastModifiedAt", t2."Priority"
FROM "People" AS t1
LEFT JOIN "TodoItems" AS t2 ON t1."Id" = t2."OwnerId"
ORDER BY t1."Id", t2."Description"
ORDER BY t2."Description"
"""));

command.Parameters.Should().BeEmpty();
Expand Down Expand Up @@ -209,7 +208,7 @@ SELECT COUNT(*)
SELECT t1."Id", t1."FirstName", t1."LastName", t2."Id", t2."CreatedAt", t2."Description", t2."DurationInHours", t2."LastModifiedAt", t2."Priority"
FROM "People" AS t1
LEFT JOIN "TodoItems" AS t2 ON t1."Id" = t2."OwnerId"
ORDER BY t1."Id", (
ORDER BY (
SELECT COUNT(*)
FROM "Tags" AS t3
WHERE t2."Id" = t3."TodoItemId"
Expand Down Expand Up @@ -263,7 +262,7 @@ SELECT COUNT(*)
FROM "People" AS t1
LEFT JOIN "TodoItems" AS t2 ON t1."Id" = t2."OwnerId"
LEFT JOIN "Tags" AS t4 ON t2."Id" = t4."TodoItemId"
ORDER BY t1."Id", (
ORDER BY (
SELECT COUNT(*)
FROM "Tags" AS t3
WHERE t2."Id" = t3."TodoItemId"
Expand Down Expand Up @@ -326,11 +325,11 @@ SELECT COUNT(*)
SELECT COUNT(*)
FROM "Tags" AS t4
WHERE t3."Id" = t4."TodoItemId"
), t5."Id", (
), (
SELECT COUNT(*)
FROM "Tags" AS t7
WHERE t6."Id" = t7."TodoItemId"
), t8."Id"
)
"""));

command.Parameters.Should().BeEmpty();
Expand Down Expand Up @@ -383,7 +382,7 @@ LEFT JOIN (
FROM "TodoItems" AS t2
WHERE t2."Description" = @p1
) AS t3 ON t1."Id" = t3."OwnerId"
ORDER BY t1."Id", t3."Priority", t3."LastModifiedAt" DESC
ORDER BY t3."Priority", t3."LastModifiedAt" DESC
"""));

command.Parameters.Should().HaveCount(1);
Expand Down Expand Up @@ -441,7 +440,7 @@ SELECT 1
WHERE t2."Id" = t3."TodoItemId"
)
) AS t4 ON t1."Id" = t4."OwnerId"
ORDER BY t1."Id", t4."Priority", t4."LastModifiedAt" DESC
ORDER BY t4."Priority", t4."LastModifiedAt" DESC
"""));

command.Parameters.Should().BeEmpty();
Expand Down Expand Up @@ -498,7 +497,7 @@ SELECT COUNT(*)
WHERE t2."Id" = t3."TodoItemId"
) > @p1
) AS t4 ON t1."Id" = t4."OwnerId"
ORDER BY t1."Id", t4."Priority", t4."LastModifiedAt" DESC
ORDER BY t4."Priority", t4."LastModifiedAt" DESC
"""));

command.Parameters.Should().HaveCount(1);
Expand Down Expand Up @@ -554,7 +553,7 @@ LEFT JOIN (
LEFT JOIN "Tags" AS t4 ON t2."Id" = t4."TodoItemId"
WHERE t2."Description" = @p1
) AS t5 ON t1."Id" = t5."OwnerId"
ORDER BY t1."Id", (
ORDER BY (
SELECT COUNT(*)
FROM "Tags" AS t3
WHERE t5."Id" = t3."TodoItemId"
Expand Down Expand Up @@ -620,7 +619,7 @@ WHERE NOT (t5."Name" = @p2)
) AS t6 ON t2."Id" = t6."TodoItemId"
WHERE NOT (t2."Description" = @p1)
) AS t7 ON t1."Id" = t7."OwnerId"
ORDER BY t1."Id", (
ORDER BY (
SELECT COUNT(*)
FROM "Tags" AS t3
WHERE t7."Id" = t3."TodoItemId"
Expand Down
Loading