Skip to content

Commit

Permalink
Merge pull request #792 from bart-degreed/complex-queries
Browse files Browse the repository at this point in the history
Composable filters and deeply nested queries
  • Loading branch information
Bart Koelman authored Aug 17, 2020
2 parents e0270bd + 1a211fa commit 72ce454
Show file tree
Hide file tree
Showing 453 changed files with 18,262 additions and 10,378 deletions.
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<!-- Test Project Dependencies -->
<PropertyGroup>
<XUnitVersion>2.4.1</XUnitVersion>
<FluentAssertionsVersion>5.10.3</FluentAssertionsVersion>
<BogusVersion>29.0.1</BogusVersion>
<MoqVersion>4.13.1</MoqVersion>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions benchmarks/BenchmarkResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Models.Annotation;

namespace Benchmarks
{
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/DependencyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Query;
using JsonApiDotNetCore.Services.Contract;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;

Expand Down
22 changes: 11 additions & 11 deletions benchmarks/LinkBuilder/LinkBuilderGetNamespaceFromPathBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ namespace Benchmarks.LinkBuilder
public class LinkBuilderGetNamespaceFromPathBenchmarks
{
private const string RequestPath = "/api/some-really-long-namespace-path/resources/current/articles/?some";
private const string EntityName = "articles";
private const string ResourceName = "articles";
private const char PathDelimiter = '/';

[Benchmark]
public void UsingStringSplit() => GetNamespaceFromPathUsingStringSplit(RequestPath, EntityName);
public void UsingStringSplit() => GetNamespaceFromPathUsingStringSplit(RequestPath, ResourceName);

[Benchmark]
public void UsingReadOnlySpan() => GetNamespaceFromPathUsingReadOnlySpan(RequestPath, EntityName);
public void UsingReadOnlySpan() => GetNamespaceFromPathUsingReadOnlySpan(RequestPath, ResourceName);

public static string GetNamespaceFromPathUsingStringSplit(string path, string entityName)
public static string GetNamespaceFromPathUsingStringSplit(string path, string resourceName)
{
StringBuilder namespaceBuilder = new StringBuilder(path.Length);
string[] segments = path.Split('/');

for (int index = 1; index < segments.Length; index++)
{
if (segments[index] == entityName)
if (segments[index] == resourceName)
{
break;
}
Expand All @@ -36,22 +36,22 @@ public static string GetNamespaceFromPathUsingStringSplit(string path, string en
return namespaceBuilder.ToString();
}

public static string GetNamespaceFromPathUsingReadOnlySpan(string path, string entityName)
public static string GetNamespaceFromPathUsingReadOnlySpan(string path, string resourceName)
{
ReadOnlySpan<char> entityNameSpan = entityName.AsSpan();
ReadOnlySpan<char> resourceNameSpan = resourceName.AsSpan();
ReadOnlySpan<char> pathSpan = path.AsSpan();

for (int index = 0; index < pathSpan.Length; index++)
{
if (pathSpan[index].Equals(PathDelimiter))
{
if (pathSpan.Length > index + entityNameSpan.Length)
if (pathSpan.Length > index + resourceNameSpan.Length)
{
ReadOnlySpan<char> possiblePathSegment = pathSpan.Slice(index + 1, entityNameSpan.Length);
ReadOnlySpan<char> possiblePathSegment = pathSpan.Slice(index + 1, resourceNameSpan.Length);

if (entityNameSpan.SequenceEqual(possiblePathSegment))
if (resourceNameSpan.SequenceEqual(possiblePathSegment))
{
int lastCharacterIndex = index + 1 + entityNameSpan.Length;
int lastCharacterIndex = index + 1 + resourceNameSpan.Length;

bool isAtEnd = lastCharacterIndex == pathSpan.Length;
bool hasDelimiterAfterSegment = pathSpan.Length >= lastCharacterIndex + 1 && pathSpan[lastCharacterIndex].Equals(PathDelimiter);
Expand Down
80 changes: 42 additions & 38 deletions benchmarks/Query/QueryParserBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Managers;
using JsonApiDotNetCore.Query;
using JsonApiDotNetCore.QueryParameterServices.Common;
using JsonApiDotNetCore.Services;
using JsonApiDotNetCore.Internal.QueryStrings;
using JsonApiDotNetCore.QueryStrings;
using JsonApiDotNetCore.RequestServices;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging.Abstractions;
Expand All @@ -17,56 +18,59 @@ namespace Benchmarks.Query
public class QueryParserBenchmarks
{
private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new FakeRequestQueryStringAccessor();
private readonly QueryParameterParser _queryParameterParserForSort;
private readonly QueryParameterParser _queryParameterParserForAll;
private readonly QueryStringReader _queryStringReaderForSort;
private readonly QueryStringReader _queryStringReaderForAll;

public QueryParserBenchmarks()
{
IJsonApiOptions options = new JsonApiOptions();
IJsonApiOptions options = new JsonApiOptions
{
EnableLegacyFilterNotation = true
};

IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);

var currentRequest = new CurrentRequest();
currentRequest.SetRequestResource(resourceGraph.GetResourceContext(typeof(BenchmarkResource)));

IResourceDefinitionProvider resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
var currentRequest = new CurrentRequest
{
PrimaryResource = resourceGraph.GetResourceContext(typeof(BenchmarkResource))
};

_queryParameterParserForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, currentRequest, resourceDefinitionProvider, options, _queryStringAccessor);
_queryParameterParserForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, currentRequest, resourceDefinitionProvider, options, _queryStringAccessor);
_queryStringReaderForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, currentRequest, options, _queryStringAccessor);
_queryStringReaderForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, currentRequest, options, _queryStringAccessor);
}

private static QueryParameterParser CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph,
CurrentRequest currentRequest, IResourceDefinitionProvider resourceDefinitionProvider,
private static QueryStringReader CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph,
CurrentRequest currentRequest,
IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
{
ISortService sortService = new SortService(resourceDefinitionProvider, resourceGraph, currentRequest);

var queryServices = new List<IQueryParameterService>
var sortReader = new SortQueryStringParameterReader(currentRequest, resourceGraph);
var readers = new List<IQueryStringParameterReader>
{
sortService
sortReader
};

return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
}

private static QueryParameterParser CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph,
CurrentRequest currentRequest, IResourceDefinitionProvider resourceDefinitionProvider,
IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph,
CurrentRequest currentRequest, IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
{
IIncludeService includeService = new IncludeService(resourceGraph, currentRequest);
IFilterService filterService = new FilterService(resourceDefinitionProvider, resourceGraph, currentRequest);
ISortService sortService = new SortService(resourceDefinitionProvider, resourceGraph, currentRequest);
ISparseFieldsService sparseFieldsService = new SparseFieldsService(resourceGraph, currentRequest);
IPageService pageService = new PageService(options, resourceGraph, currentRequest);
IDefaultsService defaultsService = new DefaultsService(options);
INullsService nullsService = new NullsService(options);

var queryServices = new List<IQueryParameterService>
var resourceFactory = new ResourceFactory(new ServiceContainer());

var filterReader = new FilterQueryStringParameterReader(currentRequest, resourceGraph, resourceFactory, options);
var sortReader = new SortQueryStringParameterReader(currentRequest, resourceGraph);
var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(currentRequest, resourceGraph);
var paginationReader = new PaginationQueryStringParameterReader(currentRequest, resourceGraph, options);
var defaultsReader = new DefaultsQueryStringParameterReader(options);
var nullsReader = new NullsQueryStringParameterReader(options);

var readers = new List<IQueryStringParameterReader>
{
includeService, filterService, sortService, sparseFieldsService, pageService, defaultsService,
nullsService
filterReader, sortReader, sparseFieldSetReader, paginationReader, defaultsReader, nullsReader
};

return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
}

[Benchmark]
Expand All @@ -75,7 +79,7 @@ public void AscendingSort()
var queryString = $"?sort={BenchmarkResourcePublicNames.NameAttr}";

_queryStringAccessor.SetQueryString(queryString);
_queryParameterParserForSort.Parse(null);
_queryStringReaderForSort.ReadAll(null);
}

[Benchmark]
Expand All @@ -84,7 +88,7 @@ public void DescendingSort()
var queryString = $"?sort=-{BenchmarkResourcePublicNames.NameAttr}";

_queryStringAccessor.SetQueryString(queryString);
_queryParameterParserForSort.Parse(null);
_queryStringReaderForSort.ReadAll(null);
}

[Benchmark]
Expand All @@ -93,7 +97,7 @@ public void ComplexQuery() => Run(100, () =>
var queryString = $"?filter[{BenchmarkResourcePublicNames.NameAttr}]=abc,eq:abc&sort=-{BenchmarkResourcePublicNames.NameAttr}&include=child&page[size]=1&fields={BenchmarkResourcePublicNames.NameAttr}";

_queryStringAccessor.SetQueryString(queryString);
_queryParameterParserForAll.Parse(null);
_queryStringReaderForAll.ReadAll(null);
});

private void Run(int iterations, Action action) {
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public JsonApiDeserializerBenchmarks()
var options = new JsonApiOptions();
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
var targetedFields = new TargetedFields();
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new DefaultResourceFactory(new ServiceContainer()), targetedFields, new HttpContextAccessor());
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new ResourceFactory(new ServiceContainer()), targetedFields, new HttpContextAccessor());
}

[Benchmark]
Expand Down
22 changes: 14 additions & 8 deletions benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Managers;
using JsonApiDotNetCore.Query;
using JsonApiDotNetCore.Internal.QueryStrings;
using JsonApiDotNetCore.Queries;
using JsonApiDotNetCore.RequestServices;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Serialization.Server;
using JsonApiDotNetCore.Serialization.Server.Builders;
Expand All @@ -14,7 +15,7 @@ namespace Benchmarks.Serialization
[MarkdownExporter]
public class JsonApiSerializerBenchmarks
{
private static readonly BenchmarkResource Content = new BenchmarkResource
private static readonly BenchmarkResource _content = new BenchmarkResource
{
Id = 123,
Name = Guid.NewGuid().ToString()
Expand All @@ -40,14 +41,19 @@ public JsonApiSerializerBenchmarks()

private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resourceGraph)
{
var resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
var currentRequest = new CurrentRequest();
var sparseFieldsService = new SparseFieldsService(resourceGraph, currentRequest);

return new FieldsToSerialize(resourceGraph, sparseFieldsService, resourceDefinitionProvider);

var constraintProviders = new IQueryConstraintProvider[]
{
new SparseFieldSetQueryStringParameterReader(currentRequest, resourceGraph)
};

var resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);

return new FieldsToSerialize(resourceGraph, constraintProviders, resourceDefinitionProvider);
}

[Benchmark]
public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content);
public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(_content);
}
}
6 changes: 3 additions & 3 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ This section documents the package API and is generated from the XML source comm

## Common APIs

- [`JsonApiOptions`](JsonApiDotNetCore.Configuration.JsonApiOptions.html)
- [`IResourceGraph`](JsonApiDotNetCore.Internal.Contracts.IResourceGraph.html)
- [`ResourceDefinition<T>`](JsonApiDotNetCore.Models.ResourceDefinition-1.html)
- [`JsonApiOptions`](JsonApiDotNetCore.Configuration.JsonApiOptions.yml)
- [`IResourceGraph`](JsonApiDotNetCore.Internal.Contracts.IResourceGraph.yml)
- [`ResourceDefinition<TResource>`](JsonApiDotNetCore.Models.ResourceDefinition-1.yml)
1 change: 1 addition & 0 deletions docs/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"getting-started/**/toc.yml",
"usage/**.md",
"request-examples/**.md",
"internals/**.md",
"toc.yml",
"*.md"
]
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ Eliminate CRUD boilerplate and provide the following features across your resour
- Filtering
- Sorting
- Pagination
- Sparse field selection
- Sparse fieldset selection
- Relationship inclusion and navigation

Checkout the [example requests](request-examples) to see the kind of features you will get out of the box.
Checkout the [example requests](request-examples/index.md) to see the kind of features you will get out of the box.

### 2. Extensibility

Expand Down
3 changes: 3 additions & 0 deletions docs/internals/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Internals

The section contains overviews for the inner workings of the JsonApiDotNetCore library.
Loading

0 comments on commit 72ce454

Please sign in to comment.