Skip to content

Commit

Permalink
Allow empty filter expressions to be visited (#5214)
Browse files Browse the repository at this point in the history
Co-authored-by: Pascal Senn <senn.pasc@gmail.com>
  • Loading branch information
david-driscoll and PascalSenn committed Aug 9, 2022
1 parent 4901fe6 commit b369d8c
Show file tree
Hide file tree
Showing 30 changed files with 313 additions and 114 deletions.
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
Expand All @@ -12,11 +11,12 @@ public class QueryableCombinator
QueryableFilterContext context,
Queue<Expression> operations,
FilterCombinator combinator,
[NotNullWhen(true)] out Expression combined)
[NotNullWhen(true)] out Expression? combined)
{
if (operations.Count == 0)
{
throw ThrowHelper.Filtering_QueryableCombinator_QueueEmpty(this);
combined = default;
return false;
}

combined = operations.Dequeue();
Expand Down
Expand Up @@ -29,6 +29,6 @@ public abstract class FilterOperationCombinator
TContext context,
Queue<T> operations,
FilterCombinator combinator,
[NotNullWhen(true)] out T combined)
[NotNullWhen(true)] out T? combined)
where TContext : FilterVisitorContext<T>;
}
Expand Up @@ -23,7 +23,7 @@ public abstract class FilterOperationCombinator<TContext, T>
TContext context,
Queue<T> operations,
FilterCombinator combinator,
[NotNullWhen(true)] out T combined);
[NotNullWhen(true)] out T? combined);

/// <inheritdoc />
public override bool TryCombineOperations<TVisitorContext, TOperation>(
Expand Down
Expand Up @@ -54,6 +54,6 @@ public FilterVisitor(FilterOperationCombinator<TContext, T> combinator)
TContext context,
Queue<T> operations,
FilterCombinator combinator,
[NotNullWhen(true)] out T combined) =>
[NotNullWhen(true)] out T? combined) =>
_combinator.TryCombineOperations(context, operations, combinator, out combined);
}
Expand Up @@ -27,7 +27,7 @@ protected FilterVisitorBase()
TContext context,
Queue<T> operations,
FilterCombinator combinator,
[NotNullWhen(true)] out T combined);
[NotNullWhen(true)] out T? combined);

protected override ISyntaxVisitorAction Leave(
ObjectValueNode node,
Expand Down
@@ -0,0 +1,58 @@
using System.Threading.Tasks;
using CookieCrumble;
using HotChocolate.Execution;

namespace HotChocolate.Data.Filters;

public class QueryableFilterCombinatorTests
{
private static readonly Foo[] _fooEntities =
{
new() { Bar = true },
new() { Bar = false }
};

private readonly SchemaCache _cache = new();

[Fact]
public async Task Create_Empty_Expression()
{
// arrange
var tester = _cache.CreateSchema<Foo, FooFilterInput>(_fooEntities);

// act
// assert
var res1 = await tester.ExecuteAsync(
QueryRequestBuilder.New()
.SetQuery("{ root(where: { }){ bar }}")
.Create());

await Snapshot.Create()
.Add(res1)
.MatchAsync();
}

public class Foo
{
public int Id { get; set; }

public bool Bar { get; set; }
}

public class FooNullable
{
public int Id { get; set; }

public bool? Bar { get; set; }
}

public class FooFilterInput
: FilterInputType<Foo>
{
}

public class FooNullableFilterInput
: FilterInputType<FooNullable>
{
}
}
@@ -0,0 +1,12 @@
{
"data": {
"root": [
{
"bar": true
},
{
"bar": false
}
]
}
}
@@ -1,19 +1,24 @@
{
"errors": [
{
"message": "Unexpected Execution Error",
"message": "The provided value for filter \u0060all\u0060 of type FooNestedFilterInput is invalid. Null values are not supported.",
"locations": [
{
"line": 2,
"column": 25
"line": 1,
"column": 35
}
],
"path": [
"root"
]
],
"extensions": {
"code": "HC0026",
"expectedType": "FooNestedFilterInput!",
"filterType": "FooNestedFilterInput"
}
}
],
"data": {
"root": null
"root": []
}
}
}
@@ -1,19 +1,24 @@
{
"errors": [
{
"message": "Unexpected Execution Error",
"message": "The provided value for filter \u0060all\u0060 of type FooNestedFilterInput is invalid. Null values are not supported.",
"locations": [
{
"line": 2,
"column": 25
"line": 1,
"column": 35
}
],
"path": [
"root"
]
],
"extensions": {
"code": "HC0026",
"expectedType": "FooNestedFilterInput!",
"filterType": "FooNestedFilterInput"
}
}
],
"data": {
"root": null
"root": []
}
}
}
@@ -1,19 +1,24 @@
{
"errors": [
{
"message": "Unexpected Execution Error",
"message": "The provided value for filter \u0060all\u0060 of type FooNestedFilterInput is invalid. Null values are not supported.",
"locations": [
{
"line": 2,
"column": 25
"line": 1,
"column": 35
}
],
"path": [
"root"
]
],
"extensions": {
"code": "HC0026",
"expectedType": "FooNestedFilterInput!",
"filterType": "FooNestedFilterInput"
}
}
],
"data": {
"root": null
"root": []
}
}
}
@@ -1,19 +1,24 @@
{
"errors": [
{
"message": "Unexpected Execution Error",
"message": "The provided value for filter \u0060all\u0060 of type FooNestedFilterInput is invalid. Null values are not supported.",
"locations": [
{
"line": 2,
"column": 25
"line": 1,
"column": 35
}
],
"path": [
"root"
]
],
"extensions": {
"code": "HC0026",
"expectedType": "FooNestedFilterInput!",
"filterType": "FooNestedFilterInput"
}
}
],
"data": {
"root": null
"root": []
}
}
}
Expand Up @@ -7,6 +7,13 @@ namespace HotChocolate.Data.MongoDb;

public abstract class MongoDbFilterDefinition : FilterDefinition<BsonDocument>
{
private static readonly MongoDbFilterDefinition _empty = new MongoDbEmptyFilterDefinition();

/// <summary>
/// Gets an empty filter. An empty filter matches everything.
/// </summary>
public static new MongoDbFilterDefinition Empty => MongoDbFilterDefinition._empty;

public abstract BsonDocument Render(
IBsonSerializer documentSerializer,
IBsonSerializerRegistry serializerRegistry);
Expand Down Expand Up @@ -52,4 +59,14 @@ public FilterDefinitionWrapper(MongoDbFilterDefinition filter)
return Render(documentSerializer, serializerRegistry);
}
}

internal sealed class MongoDbEmptyFilterDefinition : MongoDbFilterDefinition
{
public override BsonDocument Render(
IBsonSerializer documentSerializer,
IBsonSerializerRegistry serializerRegistry)
{
return new BsonDocument();
}
}
}
@@ -1,4 +1,3 @@
using System;
using System.Collections;
using MongoDB.Bson;
using MongoDB.Bson.IO;
Expand Down
@@ -1,28 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Driver;

namespace HotChocolate.Data.MongoDb.Filters;

public static class MongoDbFilterScopeExtensions
{
public static string GetPath(this MongoDbFilterScope scope) =>
string.Join(".", scope.Path.Reverse());

public static bool TryCreateQuery(
this MongoDbFilterScope scope,
[NotNullWhen(true)] out MongoDbFilterDefinition? query)
public static MongoDbFilterDefinition CreateQuery(this MongoDbFilterScope scope)
{
query = null;

if (scope.Level.Peek().Count == 0)
{
return false;
return MongoDbFilterDefinition.Empty;
}

query = new AndFilterDefinition(scope.Level.Peek().ToArray());

return true;
return new AndFilterDefinition(scope.Level.Peek().ToArray());
}
}
Expand Up @@ -10,20 +10,16 @@ public static class MongoFilterVisitorContextExtensions
/// </summary>
/// <param name="context">The context</param>
/// <returns>The current scope</returns>
public static MongoDbFilterScope GetMongoFilterScope(
this MongoDbFilterVisitorContext context) =>
(MongoDbFilterScope)context.GetScope();
public static MongoDbFilterScope GetMongoFilterScope(this MongoDbFilterVisitorContext context)
=> (MongoDbFilterScope)context.GetScope();

/// <summary>
/// Tries to build the query based on the items that are stored on the scope
/// </summary>
/// <param name="context">the context</param>
/// <param name="query">The query that was build</param>
/// <returns>True in case the query has been build successfully, otherwise false</returns>
public static bool TryCreateQuery(
this MongoDbFilterVisitorContext context,
[NotNullWhen(true)] out MongoDbFilterDefinition? query)
public static MongoDbFilterDefinition CreateQuery(this MongoDbFilterVisitorContext context)
{
return context.GetMongoFilterScope().TryCreateQuery(out query);
return context.GetMongoFilterScope().CreateQuery();
}
}
@@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using HotChocolate.Configuration;
using HotChocolate.Data.Filters;
using HotChocolate.Internal;
using HotChocolate.Language;
using HotChocolate.Language.Visitors;

Expand Down Expand Up @@ -75,8 +73,7 @@ public abstract class MongoDbListOperationHandlerBase
{
context.RuntimeTypes.Pop();

if (context.TryCreateQuery(out var query) &&
context.Scopes.Pop() is MongoDbFilterScope scope)
if (context.Scopes.Pop() is MongoDbFilterScope scope)
{
var path = context.GetMongoFilterScope().GetPath();
var combinedOperations = HandleListOperation(
Expand Down Expand Up @@ -113,8 +110,7 @@ public abstract class MongoDbListOperationHandlerBase
/// </summary>
/// <param name="scope">The scope where the definitions should be combined</param>
/// <returns>A with and combined filter definition of all definitions of the scope</returns>
protected static MongoDbFilterDefinition CombineOperationsOfScope(
MongoDbFilterScope scope)
protected static MongoDbFilterDefinition CombineOperationsOfScope(MongoDbFilterScope scope)
{
var level = scope.Level.Peek();
if (level.Count == 1)
Expand Down
@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using HotChocolate.Data.Filters;
using MongoDB.Bson;
using MongoDB.Driver;

namespace HotChocolate.Data.MongoDb.Filters;

Expand All @@ -16,11 +12,12 @@ public class MongoDbFilterCombinator
MongoDbFilterVisitorContext context,
Queue<MongoDbFilterDefinition> operations,
FilterCombinator combinator,
[NotNullWhen(true)] out MongoDbFilterDefinition combined)
[NotNullWhen(true)] out MongoDbFilterDefinition? combined)
{
if (operations.Count == 0)
{
throw ThrowHelper.Filtering_MongoDbCombinator_QueueEmpty(this);
combined = default;
return false;
}

combined = combinator switch
Expand Down

0 comments on commit b369d8c

Please sign in to comment.