Skip to content

Commit

Permalink
Adds an implementation for NpgsqlQueryOptimizer
Browse files Browse the repository at this point in the history
- Exposes a hook for adding rewriting expression visitors.

- Adds `NpgsqlExistsToAnyRewritingExpressionVisitor`.
  - Modified from `ExistsToAnyRewritingExpressionVisitor` to
    rewrite `Array.Exists<T>(T[], Predicate<T>)` as an expression
    of `Any` and `Contains`.
  • Loading branch information
austindrenski committed Jun 1, 2018
1 parent 3328d16 commit ac28fc6
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 15 deletions.
3 changes: 3 additions & 0 deletions src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.Sql;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;
Expand All @@ -50,6 +51,7 @@
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
// ReSharper disable once UnusedMember.Global
public static class NpgsqlEntityFrameworkServicesBuilderExtensions
{
/// <summary>
Expand Down Expand Up @@ -106,6 +108,7 @@ public static IServiceCollection AddEntityFrameworkNpgsql([NotNull] this IServic
.TryAdd<IMemberTranslator, NpgsqlCompositeMemberTranslator>()
.TryAdd<ICompositeMethodCallTranslator, NpgsqlCompositeMethodCallTranslator>()
.TryAdd<IExpressionFragmentTranslator, NpgsqlCompositeExpressionFragmentTranslator>()
.TryAdd<IQueryOptimizer, NpgsqlQueryOptimizer>()
.TryAdd<IQuerySqlGeneratorFactory, NpgsqlQuerySqlGeneratorFactory>()
.TryAdd<ISqlTranslatingExpressionVisitorFactory, NpgsqlSqlTranslatingExpressionVisitorFactory>()
.TryAdd<ISingletonOptions, INpgsqlOptions>(p => p.GetService<INpgsqlOptions>())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public Expression Translate(Expression expression)
if (!(expression is SubQueryExpression subQuery))
return null;

if (ContainsResult(subQuery) is Expression contains)
return contains;
// if (ContainsResult(subQuery) is Expression contains)
// return contains;

if (subQuery.QueryModel.BodyClauses.Count != 1)
return null;
Expand Down Expand Up @@ -101,6 +101,13 @@ public Expression Translate(Expression expression)
static Expression ContainsResult(SubQueryExpression expression)
{
var model = expression.QueryModel;

if (!(model.MainFromClause.FromExpression is Expression from))
return null;

if (!IsArrayOrList(from.Type))
return null;

if (model.BodyClauses.Count != 0)
return null;

Expand All @@ -110,7 +117,7 @@ static Expression ContainsResult(SubQueryExpression expression)
if (!(model.ResultOperators[0] is ContainsResultOperator contains))
return null;

return new ArrayAnyAllExpression(ArrayComparisonType.ANY, "=", contains.Item, model.MainFromClause.FromExpression);
return new ArrayAnyAllExpression(ArrayComparisonType.ANY, "=", contains.Item, from);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#region License

// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

#endregion

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Extensions.Internal;
using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors;
using Remotion.Linq;
using Remotion.Linq.Clauses;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Clauses.ResultOperators;
using Remotion.Linq.Parsing.ExpressionVisitors;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionVisitors
{
/// <summary>
/// An expression rewriter for <see cref="Array.Exists{T}(T[],Predicate{T})"/>.
/// </summary>
public class NpgsqlExistsToAnyRewritingExpressionVisitor : ExpressionVisitorBase
{
/// <summary>
/// The generic <see cref="MethodInfo"/> for <see cref="Array.Exists{T}(T[],Predicate{T})"/>.
/// </summary>
[NotNull] static readonly MethodInfo Exists =
typeof(Array).GetRuntimeMethods().Single(x => x.Name == nameof(Array.Exists));

/// <inheritdoc />
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
if (!methodCallExpression.Method.MethodIsClosedFormOf(Exists))
return methodCallExpression;

if (!(methodCallExpression.Arguments[0] is Expression array))
return methodCallExpression;

if (!(methodCallExpression.Arguments[1] is LambdaExpression predicate))
return methodCallExpression;

var mainFromClause =
new MainFromClause(
"<array_item>",
array.Type.GetElementType(),
array);

var qsre = new QuerySourceReferenceExpression(mainFromClause);
var queryModel = new QueryModel(mainFromClause, new SelectClause(qsre));

var where =
new WhereClause(
ReplacingExpressionVisitor.Replace(
predicate.Parameters[0],
qsre,
predicate.Body));

queryModel.BodyClauses.Add(where);
queryModel.ResultOperators.Add(new AnyResultOperator());
queryModel.ResultTypeOverride = typeof(bool);

return new SubQueryExpression(queryModel);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ protected virtual Expression VisitArraySubQuery([NotNull] SubQueryExpression exp
case ConcatResultOperator concatResultOperator:
return VisitArrayConcat(array, concatResultOperator);

case ContainsResultOperator contains:
return new ArrayAnyAllExpression(ArrayComparisonType.ANY, "=", Visit(contains.Item) ?? contains.Item, array);

case CountResultOperator countResultOperator:
return VisitArrayCount(array, countResultOperator);

Expand Down
58 changes: 58 additions & 0 deletions src/EFCore.PG/Query/Internal/NpgsqlQueryOptimizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#region License

// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

#endregion

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionVisitors;
using Remotion.Linq;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal
{
/// <summary>
/// The default relational LINQ query optimizer for Npgsql.
/// </summary>
public class NpgsqlQueryOptimizer : QueryOptimizer
{
/// <summary>
/// The default expression visitors registered by the Npgsql provider.
/// </summary>
static readonly ExpressionVisitor[] ExpressionVisitors =
{
new NpgsqlExistsToAnyRewritingExpressionVisitor()
};

/// <inheritdoc />
public override void Optimize(QueryCompilationContext queryCompilationContext, QueryModel queryModel)
{
base.Optimize(queryCompilationContext, queryModel);

for (int i = 0; i < ExpressionVisitors.Length; i++)
{
queryModel.TransformExpressions(ExpressionVisitors[i].Visit);
}
}
}
}
16 changes: 4 additions & 12 deletions test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ public void Array_Exists_equals_with_parameter_array_and_column_array_element()
{
var array = new[] { 0, 1, 2 };
var _ = ctx.SomeEntities.Where(x => Array.Exists(array, y => y == x.SomeArray[0])).ToList();
AssertContainsInSql(@"WHERE x.""SomeArray""[1] = ANY (@__array_0) = TRUE");
AssertContainsInSql(@"WHERE x.""SomeArray""[1] IN (0, 1, 2)");
}
}

Expand All @@ -687,11 +687,7 @@ public void List_Exists_equals_with_parameter_array_and_column_list_element()
{
var list = new List<int> { 0, 1, 2 };
var _ = ctx.SomeEntities.Where(x => list.Exists(y => y == x.SomeList[0])).ToList();

// What we don't want:
AssertDoesNotContainInSql(@"WHERE (x.""SomeList""[1]) IN (0, 1, 2)");
// What we do want:
AssertContainsInSql(@"WHERE x.""SomeList""[1] = ANY (@__list_0) = TRUE");
AssertContainsInSql(@"WHERE x.""SomeList""[1] IN (0, 1, 2)");
}
}

Expand All @@ -702,7 +698,7 @@ public void Array_Exists_equals_with_parameter_array_and_column_array_element_fl
{
var array = new[] { 0, 1, 2 };
var _ = ctx.SomeEntities.Where(x => Array.Exists(array, y => x.SomeArray[0] == y)).ToList();
AssertContainsInSql(@"WHERE x.""SomeArray""[1] = ANY (@__array_0) = TRUE");
AssertContainsInSql(@"WHERE x.""SomeArray""[1] IN (0, 1, 2)");
}
}

Expand All @@ -713,11 +709,7 @@ public void List_Exists_equals_with_parameter_array_and_column_list_element_flip
{
var list = new List<int> { 0, 1, 2 };
var _ = ctx.SomeEntities.Where(x => list.Exists(y => x.SomeList[0] == y)).ToList();

// What we don't want:
AssertDoesNotContainInSql(@"WHERE (x.""SomeList""[1]) IN (0, 1, 2)");
// What we do want:
AssertContainsInSql(@"WHERE x.""SomeList""[1] = ANY (@__list_0) = TRUE");
AssertContainsInSql(@"WHERE x.""SomeList""[1] IN (0, 1, 2)");
}
}

Expand Down

0 comments on commit ac28fc6

Please sign in to comment.