Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set operation support (except navigation/include) #16127

Merged
merged 1 commit into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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

5 changes: 4 additions & 1 deletion src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -481,4 +481,7 @@
<data name="PendingAmbientTransaction" xml:space="preserve">
<value>This connection was used with an ambient transaction. The original ambient transaction needs to be completed before this connection can be used outside of it.</value>
</data>
</root>
<data name="SetOperationNotWithinEntityTypeHierarchy" xml:space="preserve">
<value>Set operations (Union, Concat, Intersect, Except) are only supported over entity types within the same type hierarchy.</value>
</data>
</root>
124 changes: 96 additions & 28 deletions src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
Expand Down Expand Up @@ -79,6 +80,28 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
subQueryIndent = _relationalCommandBuilder.Indent();
}

if (selectExpression.IsSetOperation)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are not doing a lot of process in between, just use using syntax for Indent

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only want to indent for subqueries (when alias != null)...

{
GenerateSetOperation(selectExpression);
}
else
{
GenerateSelect(selectExpression);
}

if (selectExpression.Alias != null)
{
subQueryIndent.Dispose();

_relationalCommandBuilder.AppendLine()
.Append(") AS " + _sqlGenerationHelper.DelimitIdentifier(selectExpression.Alias));
}

return selectExpression;
}

protected virtual void GenerateSelect(SelectExpression selectExpression)
{
_relationalCommandBuilder.Append("SELECT ");

if (selectExpression.IsDistinct)
Expand Down Expand Up @@ -111,40 +134,55 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
Visit(selectExpression.Predicate);
}

if (selectExpression.Orderings.Any())
{
var orderings = selectExpression.Orderings.ToList();
GenerateOrderings(selectExpression);
GenerateLimitOffset(selectExpression);
}

if (selectExpression.Limit == null
&& selectExpression.Offset == null)
{
orderings.RemoveAll(oe => oe.Expression is SqlConstantExpression || oe.Expression is SqlParameterExpression);
}
protected virtual void GenerateSetOperation(SelectExpression setOperationExpression)
{
Debug.Assert(setOperationExpression.Tables.Count == 2,
$"{nameof(SelectExpression)} with {setOperationExpression.Tables.Count} tables, must be 2");

if (orderings.Count > 0)
{
_relationalCommandBuilder.AppendLine()
.Append("ORDER BY ");
GenerateSetOperationOperand(setOperationExpression, (SelectExpression)setOperationExpression.Tables[0]);

GenerateList(orderings, e => Visit(e));
}
}
else if (selectExpression.Offset != null)
{
_relationalCommandBuilder.AppendLine().Append("ORDER BY (SELECT 1)");
}
_relationalCommandBuilder
.AppendLine()
.AppendLine(GenerateSetOperationType(setOperationExpression.SetOperationType));

GenerateLimitOffset(selectExpression);
GenerateSetOperationOperand(setOperationExpression, (SelectExpression)setOperationExpression.Tables[1]);

if (selectExpression.Alias != null)
{
subQueryIndent.Dispose();
GenerateOrderings(setOperationExpression);
GenerateLimitOffset(setOperationExpression);
}

_relationalCommandBuilder.AppendLine()
.Append(") AS " + _sqlGenerationHelper.DelimitIdentifier(selectExpression.Alias));
private static string GenerateSetOperationType(SetOperationType setOperationType)
=> setOperationType switch {
SetOperationType.Union => "UNION",
SetOperationType.UnionAll => "UNION ALL",
SetOperationType.Intersect => "INTERSECT",
SetOperationType.Except => "EXCEPT",
_ => throw new NotSupportedException($"Invalid {nameof(SetOperationType)}: {setOperationType}")
};

protected virtual void GenerateSetOperationOperand(
SelectExpression setOperationExpression,
SelectExpression operandExpression)
{
// INTERSECT has higher precedence over UNION and EXCEPT, but otherwise evaluation is left-to-right.
// To preserve meaning, add parentheses whenever a set operation is nested within a different set operation.
if (operandExpression.IsSetOperation
&& operandExpression.SetOperationType != setOperationExpression.SetOperationType)
{
_relationalCommandBuilder.AppendLine("(");
using (_relationalCommandBuilder.Indent())
{
Visit(operandExpression);
}
_relationalCommandBuilder.AppendLine().Append(")");
return;
}

return selectExpression;
Visit(operandExpression);
}

protected override Expression VisitProjection(ProjectionExpression projectionExpression)
Expand Down Expand Up @@ -198,9 +236,13 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction

protected override Expression VisitColumn(ColumnExpression columnExpression)
{
if (columnExpression.Table.Alias != null)
roji marked this conversation as resolved.
Show resolved Hide resolved
roji marked this conversation as resolved.
Show resolved Hide resolved
{
_relationalCommandBuilder
.Append(_sqlGenerationHelper.DelimitIdentifier(columnExpression.Table.Alias))
.Append(".");
}
_relationalCommandBuilder
.Append(_sqlGenerationHelper.DelimitIdentifier(columnExpression.Table.Alias))
.Append(".")
.Append(_sqlGenerationHelper.DelimitIdentifier(columnExpression.Name));

return columnExpression;
Expand Down Expand Up @@ -538,6 +580,32 @@ protected virtual void GenerateTop(SelectExpression selectExpression)
{
}

protected virtual void GenerateOrderings(SelectExpression selectExpression)
{
if (selectExpression.Orderings.Any())
{
var orderings = selectExpression.Orderings.ToList();

if (selectExpression.Limit == null
&& selectExpression.Offset == null)
{
orderings.RemoveAll(oe => oe.Expression is SqlConstantExpression || oe.Expression is SqlParameterExpression);
}

if (orderings.Count > 0)
{
_relationalCommandBuilder.AppendLine()
.Append("ORDER BY ");

GenerateList(orderings, e => Visit(e));
}
}
else if (selectExpression.Offset != null)
{
_relationalCommandBuilder.AppendLine().Append("ORDER BY (SELECT 1)");
}
}

protected virtual void GenerateLimitOffset(SelectExpression selectExpression)
{
// The below implements ISO SQL:2008
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.EntityFrameworkCore.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline
{
Expand Down Expand Up @@ -150,7 +151,13 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
return source;
}

protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression source1, ShapedQueryExpression source2) => throw new NotImplementedException();
protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression source1, ShapedQueryExpression source2)
{
var operand1 = (SelectExpression)source1.QueryExpression;
var operand2 = (SelectExpression)source2.QueryExpression;
source1.ShaperExpression = operand1.ApplySetOperation(SetOperationType.UnionAll, operand2, source1.ShaperExpression);
return source1;
}

protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression source, Expression item)
{
Expand Down Expand Up @@ -212,7 +219,13 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression

protected override ShapedQueryExpression TranslateElementAtOrDefault(ShapedQueryExpression source, Expression index, bool returnDefault) => throw new NotImplementedException();

protected override ShapedQueryExpression TranslateExcept(ShapedQueryExpression source1, ShapedQueryExpression source2) => throw new NotImplementedException();
protected override ShapedQueryExpression TranslateExcept(ShapedQueryExpression source1, ShapedQueryExpression source2)
{
var operand1 = (SelectExpression)source1.QueryExpression;
var operand2 = (SelectExpression)source2.QueryExpression;
source1.ShaperExpression = operand1.ApplySetOperation(SetOperationType.Except, operand2, source1.ShaperExpression);
return source1;
}

protected override ShapedQueryExpression TranslateFirstOrDefault(ShapedQueryExpression source, LambdaExpression predicate, Type returnType, bool returnDefault)
{
Expand Down Expand Up @@ -279,7 +292,13 @@ protected override ShapedQueryExpression TranslateGroupJoin(ShapedQueryExpressio
throw new NotImplementedException();
}

protected override ShapedQueryExpression TranslateIntersect(ShapedQueryExpression source1, ShapedQueryExpression source2) => throw new NotImplementedException();
protected override ShapedQueryExpression TranslateIntersect(ShapedQueryExpression source1, ShapedQueryExpression source2)
{
var operand1 = (SelectExpression)source1.QueryExpression;
var operand2 = (SelectExpression)source2.QueryExpression;
source1.ShaperExpression = operand1.ApplySetOperation(SetOperationType.Intersect, operand2, source1.ShaperExpression);
return source1;
}

protected override ShapedQueryExpression TranslateJoin(
ShapedQueryExpression outer,
Expand Down Expand Up @@ -730,7 +749,13 @@ protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression s
throw new InvalidOperationException();
}

protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression source1, ShapedQueryExpression source2) => throw new NotImplementedException();
protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression source1, ShapedQueryExpression source2)
{
var operand1 = (SelectExpression)source1.QueryExpression;
var operand2 = (SelectExpression)source2.QueryExpression;
source1.ShaperExpression = operand1.ApplySetOperation(SetOperationType.Union, operand2, source1.ShaperExpression);
return source1;
}

protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,14 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
public ColumnExpression MakeNullable()
=> new ColumnExpression(Name, Table, Type.MakeNullable(), TypeMapping, true);


public override void Print(ExpressionPrinter expressionPrinter)
=> expressionPrinter.StringBuilder.Append(Table.Alias).Append(".").Append(Name);
{
if (Table.Alias != null)
{
expressionPrinter.StringBuilder.Append(Table.Alias).Append(".");
}
expressionPrinter.StringBuilder.Append(Name);
}

public override bool Equals(object obj)
=> obj != null
Expand All @@ -66,6 +71,7 @@ private bool Equals(ColumnExpression columnExpression)

public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Name, Table, Nullable);

private string DebuggerDisplay() => $"{Table.Alias}.{Name}";
private string DebuggerDisplay()
=> Table.Alias == null ? Name : $"{Table.Alias}.{Name}";
}
}
Loading