Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Apr 28, 2023
1 parent 2edfca8 commit f16a6db
Show file tree
Hide file tree
Showing 20 changed files with 519 additions and 331 deletions.
18 changes: 13 additions & 5 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@
<data name="CannotChangeWhenOpen" xml:space="preserve">
<value>The instance of DbConnection is currently in use. The connection can only be changed when the existing connection is not being used.</value>
</data>
<data name="CannotTranslateNonConstantNewArrayExpression" xml:space="preserve">
<value>The query contained a new array expression containing non-constant elements, which could not be translated: '{newArrayExpression}'.</value>
</data>
<data name="CannotConfigureTriggerNonRootTphEntity" xml:space="preserve">
<value>Can't configure a trigger on entity type '{entityType}', which is in a TPH hierarchy and isn't the root. Configure the trigger on the TPH root entity type '{rootEntityType}' instead.</value>
</data>
Expand Down Expand Up @@ -346,8 +349,8 @@
<data name="DuplicateSeedDataSensitive" xml:space="preserve">
<value>A seed entity for entity type '{entityType}' has the same key value {keyValue} as another seed entity mapped to the same table '{table}'. Key values should be unique across seed entities.</value>
</data>
<data name="EitherOfTwoValuesMustBeNull" xml:space="preserve">
<value>Either {param1} or {param2} must be null.</value>
<data name="OneOfThreeValuesMustBeSet" xml:space="preserve">
<value>Exactly one of '{param1}', '{param2}' or '{param3}' must be set.</value>
</data>
<data name="EmptyCollectionNotSupportedAsInlineQueryRoot" xml:space="preserve">
<value>Empty collections are not supported as inline query roots.</value>
Expand Down Expand Up @@ -912,7 +915,7 @@
<value>Cannot create a DbCommand for a non-relational query.</value>
</data>
<data name="NonConstantOrParameterAsInExpressionValues" xml:space="preserve">
<value>Expression of type '{type}' isn't supported as the Values of an InExpression; only constants and parameters are supported.</value>
<value>Expression of type '{type}' isn't supported in the Values of an InExpression; only constants and parameters are supported.</value>
</data>
<data name="NoneRelationalTypeMappingOnARelationalTypeMappingSource" xml:space="preserve">
<value>'FindMapping' was called on a 'RelationalTypeMappingSource' with a non-relational 'TypeMappingInfo'.</value>
Expand Down
15 changes: 12 additions & 3 deletions src/EFCore.Relational/Query/ISqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,23 +403,32 @@ SqlFunctionExpression NiladicFunction(
/// <returns>An expression representing an EXISTS operation in a SQL tree.</returns>
ExistsExpression Exists(SelectExpression subquery, bool negated);

/// <summary>
/// Creates a new <see cref="InExpression" /> which represents an IN operation in a SQL tree.
/// </summary>
/// <param name="item">An item to look into values.</param>
/// <param name="subquery">A subquery in which item is searched.</param>
/// <param name="negated">A value indicating if the item should be present in the values or absent.</param>
/// <returns>An expression representing an IN operation in a SQL tree.</returns>
InExpression In(SqlExpression item, SelectExpression subquery, bool negated);

/// <summary>
/// Creates a new <see cref="InExpression" /> which represents an IN operation in a SQL tree.
/// </summary>
/// <param name="item">An item to look into values.</param>
/// <param name="values">A list of values in which item is searched.</param>
/// <param name="negated">A value indicating if the item should be present in the values or absent.</param>
/// <returns>An expression representing an IN operation in a SQL tree.</returns>
InExpression In(SqlExpression item, SqlExpression values, bool negated);
InExpression In(SqlExpression item, IReadOnlyList<SqlExpression> values, bool negated);

/// <summary>
/// Creates a new <see cref="InExpression" /> which represents an IN operation in a SQL tree.
/// </summary>
/// <param name="item">An item to look into values.</param>
/// <param name="subquery">A subquery in which item is searched.</param>
/// <param name="valuesParameter">A parameterized list of values in which the item is searched.</param>
/// <param name="negated">A value indicating if the item should be present in the values or absent.</param>
/// <returns>An expression representing an IN operation in a SQL tree.</returns>
InExpression In(SqlExpression item, SelectExpression subquery, bool negated);
InExpression In(SqlExpression item, SqlParameterExpression valuesParameter, bool negated);

/// <summary>
/// Creates a new <see cref="InExpression" /> which represents a LIKE in a SQL tree.
Expand Down
31 changes: 27 additions & 4 deletions src/EFCore.Relational/Query/Internal/ContainsTranslator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query.Internal;
Expand Down Expand Up @@ -38,26 +39,48 @@ public ContainsTranslator(ISqlExpressionFactory sqlExpressionFactory)
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
SqlExpression? itemExpression = null, valuesExpression = null;
// SqlExpression? values = null;
if (method.IsGenericMethod
&& method.GetGenericMethodDefinition().Equals(EnumerableMethods.Contains)
&& method.GetGenericMethodDefinition() == EnumerableMethods.Contains
&& ValidateValues(arguments[0]))
{
return _sqlExpressionFactory.In(RemoveObjectConvert(arguments[1]), arguments[0], negated: false);
(itemExpression, valuesExpression) = (RemoveObjectConvert(arguments[1]), arguments[0]);
}

if (arguments.Count == 1
&& method.IsContainsMethod()
&& instance != null
&& ValidateValues(instance))
{
return _sqlExpressionFactory.In(RemoveObjectConvert(arguments[0]), instance, negated: false);
(itemExpression, valuesExpression) = (RemoveObjectConvert(arguments[0]), instance);
}

if (itemExpression is not null && valuesExpression is not null)
{
switch (valuesExpression)
{
case SqlParameterExpression parameter:
return _sqlExpressionFactory.In(itemExpression, parameter, negated: false);

case SqlConstantExpression { Value: IEnumerable values }:
var valuesExpressions = new List<SqlExpression>();

foreach (var value in values)
{
// TODO: Type mapping?
valuesExpressions.Add(_sqlExpressionFactory.Constant(value, itemExpression.TypeMapping));
}

return _sqlExpressionFactory.In(itemExpression, valuesExpressions, negated: false);
}
}

return null;
}

private static bool ValidateValues(SqlExpression values)
=> values is SqlConstantExpression || values is SqlParameterExpression;
=> values is SqlConstantExpression or SqlParameterExpression;

private static SqlExpression RemoveObjectConvert(SqlExpression expression)
=> expression is SqlUnaryExpression sqlUnaryExpression
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,18 +245,11 @@ or ExpressionType.LessThan
&& leftCandidateInfo.ColumnExpression == rightCandidateInfo.ColumnExpression
&& leftCandidateInfo.OperationType == rightCandidateInfo.OperationType)
{
var leftConstantIsEnumerable = leftCandidateInfo.ConstantValue is IEnumerable
&& !(leftCandidateInfo.ConstantValue is string)
&& !(leftCandidateInfo.ConstantValue is byte[]);

var rightConstantIsEnumerable = rightCandidateInfo.ConstantValue is IEnumerable
&& !(rightCandidateInfo.ConstantValue is string)
&& !(rightCandidateInfo.ConstantValue is byte[]);

if ((leftCandidateInfo.OperationType == ExpressionType.Equal
&& sqlBinaryExpression.OperatorType == ExpressionType.OrElse)
|| (leftCandidateInfo.OperationType == ExpressionType.NotEqual
&& sqlBinaryExpression.OperatorType == ExpressionType.AndAlso))
var leftConstantIsEnumerable = leftCandidateInfo.ConstantValue is IEnumerable and not string and not byte[];
var rightConstantIsEnumerable = rightCandidateInfo.ConstantValue is IEnumerable and not string and not byte[];

if ((leftCandidateInfo.OperationType, sqlBinaryExpression.OperatorType) is
(ExpressionType.Equal, ExpressionType.OrElse) or (ExpressionType.NotEqual, ExpressionType.AndAlso))
{
object leftValue;
object rightValue;
Expand Down Expand Up @@ -309,7 +302,7 @@ or ExpressionType.LessThan

return _sqlExpressionFactory.In(
leftCandidateInfo.ColumnExpression,
_sqlExpressionFactory.Constant(resultArray, leftCandidateInfo.TypeMapping),
resultArray.Select(r => _sqlExpressionFactory.Constant(r, leftCandidateInfo.TypeMapping)).ToArray(),
leftCandidateInfo.OperationType == ExpressionType.NotEqual);
}

Expand All @@ -323,7 +316,7 @@ or ExpressionType.LessThan

return _sqlExpressionFactory.In(
leftCandidateInfo.ColumnExpression,
_sqlExpressionFactory.Constant(resultArray, leftCandidateInfo.TypeMapping),
resultArray.Select(r => _sqlExpressionFactory.Constant(r, leftCandidateInfo.TypeMapping)).ToArray(),
leftCandidateInfo.OperationType == ExpressionType.NotEqual);
}
}
Expand Down
24 changes: 11 additions & 13 deletions src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -927,31 +927,29 @@ protected override Expression VisitExists(ExistsExpression existsExpression)
/// <inheritdoc />
protected override Expression VisitIn(InExpression inExpression)
{
if (inExpression.Values != null)
Check.DebugAssert(inExpression.ValuesParameter is null, "inExpression.ValuesParameter is null");

Visit(inExpression.Item);
_relationalCommandBuilder.Append(inExpression.IsNegated ? " NOT IN (" : " IN (");

if (inExpression.Values is not null)
{
Visit(inExpression.Item);
_relationalCommandBuilder.Append(inExpression.IsNegated ? " NOT IN " : " IN ");
_relationalCommandBuilder.Append("(");
var valuesConstant = (SqlConstantExpression)inExpression.Values;
var valuesList = ((IEnumerable<object?>)valuesConstant.Value!)
.Select(v => new SqlConstantExpression(Expression.Constant(v), valuesConstant.TypeMapping)).ToList();
GenerateList(valuesList, e => Visit(e));
_relationalCommandBuilder.Append(")");
GenerateList(inExpression.Values, e => Visit(e));
}
else
{
Visit(inExpression.Item);
_relationalCommandBuilder.Append(inExpression.IsNegated ? " NOT IN " : " IN ");
_relationalCommandBuilder.AppendLine("(");
_relationalCommandBuilder.AppendLine();

using (_relationalCommandBuilder.Indent())
{
Visit(inExpression.Subquery);
}

_relationalCommandBuilder.AppendLine().Append(")");
_relationalCommandBuilder.AppendLine();
}

_relationalCommandBuilder.Append(")");

return inExpression;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public RelationalQueryRootProcessor(
/// Indicates that a <see cref="ConstantExpression" /> can be converted to a <see cref="InlineQueryRootExpression" />;
/// this will later be translated to a SQL <see cref="ValuesExpression" />.
/// </summary>
protected override bool ShouldConvertToInlineQueryRoot(ConstantExpression constantExpression)
protected override bool ShouldConvertToInlineQueryRoot(NewArrayExpression newArrayExpression)
=> true;

/// <inheritdoc />
Expand Down
Loading

0 comments on commit f16a6db

Please sign in to comment.