Skip to content

Commit

Permalink
Map IList operations to PostgreSQL arrays
Browse files Browse the repository at this point in the history
- First goal: feature parity with mapped `T[]` operations.
  - 10 of 10 unit tests passing:
    - Passes
      - Roundtrip
      - SequenceEqual
      - Length/Count
      - Contains/Any
      - Indexers

- Issues:
  - Blocked by #430
  - There should be a better system for these types of mappings.
    - Along the lines of `IMethodCallTranslator`.
  • Loading branch information
austindrenski committed May 29, 2018
1 parent d39db2e commit dd93a04
Show file tree
Hide file tree
Showing 9 changed files with 499 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#region License

// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
Expand All @@ -19,8 +20,10 @@
// 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.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
Expand All @@ -37,23 +40,26 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte
/// </remarks>
public class NpgsqlArraySequenceEqualTranslator : IMethodCallTranslator
{
static readonly MethodInfo SequenceEqualMethodInfo = typeof(Enumerable).GetTypeInfo().GetDeclaredMethods(nameof(Enumerable.SequenceEqual)).Single(m =>
m.IsGenericMethodDefinition &&
m.GetParameters().Length == 2
);
static readonly MethodInfo SequenceEqualMethodInfo =
typeof(Enumerable)
.GetTypeInfo()
.GetDeclaredMethods(nameof(Enumerable.SequenceEqual))
.Single(m =>
m.IsGenericMethodDefinition &&
m.GetParameters().Length == 2);

[CanBeNull]
public Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method;
if (method.IsGenericMethod &&
ReferenceEquals(method.GetGenericMethodDefinition(), SequenceEqualMethodInfo) &&
methodCallExpression.Arguments.All(a => a.Type.IsArray))
{
return Expression.MakeBinary(ExpressionType.Equal,
methodCallExpression.Arguments[0],
methodCallExpression.Arguments[1]);
}
methodCallExpression.Arguments.All(a => a.Type.IsArray || typeof(IList).IsAssignableFrom(a.Type)))
return
Expression.MakeBinary(
ExpressionType.Equal,
methodCallExpression.Arguments[0],
methodCallExpression.Arguments[1]);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#region License

// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
Expand All @@ -19,6 +20,7 @@
// 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.Collections.Generic;
Expand All @@ -28,23 +30,37 @@

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal
{
/// <summary>
/// A composite member translator that dispatches to multiple specialized member translators specific to Npgsql.
/// </summary>
public class NpgsqlCompositeMemberTranslator : RelationalCompositeMemberTranslator
{
/// <summary>
/// The default member translators registered by the Npgsql provider.
/// </summary>
static readonly IMemberTranslator[] MemberTranslators =
{
new NpgsqlStringLengthTranslator(),
new NpgsqlDateTimeMemberTranslator()
};

/// <inheritdoc />
public NpgsqlCompositeMemberTranslator(
[NotNull] RelationalCompositeMemberTranslatorDependencies dependencies,
[NotNull] INpgsqlOptions npgsqlOptions)
: base(dependencies)
{
AddTranslators(new List<IMemberTranslator>
{
new NpgsqlStringLengthTranslator(),
new NpgsqlDateTimeMemberTranslator()
});
// ReSharper disable once VirtualMemberCallInConstructor
AddTranslators(MemberTranslators);

foreach (var plugin in npgsqlOptions.Plugins)
plugin.AddMemberTranslators(this);
}

/// <summary>
/// Adds additional dispatches to the translators list.
/// </summary>
/// <param name="translators">The translators.</param>
public new virtual void AddTranslators([NotNull] IEnumerable<IMemberTranslator> translators)
=> base.AddTranslators(translators);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#region License

// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
Expand All @@ -19,6 +20,7 @@
// 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.Collections.Generic;
Expand All @@ -28,9 +30,15 @@

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal
{
/// <summary>
/// A composite method call translator that dispatches to multiple specialized method call translators specific to Npgsql.
/// </summary>
public class NpgsqlCompositeMethodCallTranslator : RelationalCompositeMethodCallTranslator
{
static readonly IMethodCallTranslator[] _methodCallTranslators =
/// <summary>
/// The default method call translators registered by the Npgsql provider.
/// </summary>
static readonly IMethodCallTranslator[] MethodCallTranslators =
{
new NpgsqlArraySequenceEqualTranslator(),
new NpgsqlConvertTranslator(),
Expand All @@ -52,21 +60,27 @@ public class NpgsqlCompositeMethodCallTranslator : RelationalCompositeMethodCall
new NpgsqlStringTrimStartTranslator(),
new NpgsqlRegexIsMatchTranslator(),
new NpgsqlFullTextSearchMethodTranslator(),
new NpgsqlRangeTranslator()
new NpgsqlRangeTranslator(),
new NpgsqlListTranslator()
};

/// <inheritdoc />
public NpgsqlCompositeMethodCallTranslator(
[NotNull] RelationalCompositeMethodCallTranslatorDependencies dependencies,
[NotNull] INpgsqlOptions npgsqlOptions)
: base(dependencies)
{
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
AddTranslators(_methodCallTranslators);
AddTranslators(MethodCallTranslators);

foreach (var plugin in npgsqlOptions.Plugins)
plugin.AddMethodCallTranslators(this);
}

/// <summary>
/// Adds additional dispatches to the translators list.
/// </summary>
/// <param name="translators">The translators.</param>
public new virtual void AddTranslators([NotNull] IEnumerable<IMethodCallTranslator> translators)
=> base.AddTranslators(translators);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#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.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal
{
/// <summary>
/// Provides translation services for <see cref="List{T}"/> methods as PostgreSQL array operators.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/functions-array.html
/// </remarks>
public class NpgsqlListTranslator : IMethodCallTranslator
{
/// <inheritdoc />
[CanBeNull]
public Expression Translate(MethodCallExpression expression)
{
if (!typeof(IList).IsAssignableFrom(expression.Method.DeclaringType))
return null;

switch (expression.Method.Name)
{
case "get_Item" when expression.Object is Expression instance:
return Expression.MakeIndex(instance, instance.Type.GetRuntimeProperty("Item"), expression.Arguments);
// case nameof(NpgsqlListExtensions.Contains):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "@>", typeof(bool));
//
// case nameof(NpgsqlListExtensions.ContainedBy):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "<@", typeof(bool));
//
// case nameof(NpgsqlListExtensions.Overlaps):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "&&", typeof(bool));
//
// case nameof(NpgsqlListExtensions.IsStrictlyLeftOf):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "<<", typeof(bool));
//
// case nameof(NpgsqlListExtensions.IsStrictlyRightOf):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], ">>", typeof(bool));
//
// case nameof(NpgsqlListExtensions.DoesNotExtendRightOf):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "&<", typeof(bool));
//
// case nameof(NpgsqlListExtensions.DoesNotExtendLeftOf):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "&>", typeof(bool));
//
// case nameof(NpgsqlListExtensions.IsAdjacentTo):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "-|-", typeof(bool));
//
// case nameof(NpgsqlListExtensions.Union):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "+", expression.Arguments[0].Type);
//
// case nameof(NpgsqlListExtensions.Intersect):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "*", expression.Arguments[0].Type);
//
// case nameof(NpgsqlListExtensions.Except):
// return new CustomBinaryExpression(expression.Arguments[0], expression.Arguments[1], "-", expression.Arguments[0].Type);

default:
return null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#region License

// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
Expand All @@ -19,8 +20,10 @@
// 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.Collections;
using System.Linq;
using System.Linq.Expressions;
using JetBrains.Annotations;
Expand All @@ -34,10 +37,14 @@

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionVisitors
{
/// <summary>
/// The default relational LINQ translating expression visitor for Npgsql.
/// </summary>
public class NpgsqlSqlTranslatingExpressionVisitor : SqlTranslatingExpressionVisitor
{
private readonly RelationalQueryModelVisitor _queryModelVisitor;

/// <inheritdoc />
public NpgsqlSqlTranslatingExpressionVisitor(
[NotNull] SqlTranslatingExpressionVisitorDependencies dependencies,
[NotNull] RelationalQueryModelVisitor queryModelVisitor,
Expand All @@ -49,6 +56,8 @@ public NpgsqlSqlTranslatingExpressionVisitor(
_queryModelVisitor = queryModelVisitor;
}

/// <inheritdoc />
[CanBeNull]
protected override Expression VisitSubQuery(SubQueryExpression expression)
{
// Prefer the default EF Core translation if one exists
Expand All @@ -65,24 +74,22 @@ protected override Expression VisitSubQuery(SubQueryExpression expression)
if (properties.Count == 0)
return null;
var lastPropertyType = properties[properties.Count - 1].ClrType;
if (lastPropertyType.IsArray && lastPropertyType.GetArrayRank() == 1 && subQueryModel.ResultOperators.Count > 0)
if (typeof(IList).IsAssignableFrom(lastPropertyType) && subQueryModel.ResultOperators.Count > 0)
{
// Translate someArray.Length
if (subQueryModel.ResultOperators.First() is CountResultOperator)
return Expression.ArrayLength(Visit(fromExpression));
return new SqlFunctionExpression("array_length", typeof(int), new[] { Visit(fromExpression), Expression.Constant(1) });

// Translate someArray.Contains(someValue)
if (subQueryModel.ResultOperators.First() is ContainsResultOperator contains)
{
var containsItem = Visit(contains.Item);
if (containsItem != null)
return new ArrayAnyExpression(containsItem, Visit(fromExpression));
}
if (Visit(contains.Item) is Expression containsItem && Visit(fromExpression) is Expression source)
return new ArrayAnyExpression(containsItem, source);
}

return null;
}

/// <inheritdoc />
protected override Expression VisitBinary(BinaryExpression expression)
{
if (expression.NodeType == ExpressionType.ArrayIndex)
Expand All @@ -102,6 +109,7 @@ protected override Expression VisitBinary(BinaryExpression expression)
: null;
}
}

return base.VisitBinary(expression);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#region License

// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
Expand All @@ -19,10 +20,10 @@
// 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.Diagnostics;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Sql.Internal;
Expand All @@ -49,7 +50,6 @@ public ArrayAnyExpression(
{
Check.NotNull(operand, nameof(operand));
Check.NotNull(array, nameof(array));
Debug.Assert(array.Type.IsArray);

Operand = operand;
Array = array;
Expand Down
Loading

0 comments on commit dd93a04

Please sign in to comment.