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

Query: Introduce QueryableFunctionExpression #20183

Merged
merged 1 commit into from
Mar 5, 2020
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
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Query/ISqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ SqlFunctionExpression Function(

SelectExpression Select([CanBeNull] SqlExpression projection);
SelectExpression Select([NotNull] IEntityType entityType);
SelectExpression Select([NotNull] IEntityType entityType, [NotNull] TableExpressionBase tableExpressionBase);
SelectExpression Select([NotNull] IEntityType entityType, [NotNull] string sql, [NotNull] Expression sqlArguments);
SelectExpression Select([NotNull] IEntityType entityType, [NotNull] SqlFunctionExpression expression);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -446,12 +446,12 @@ protected override Expression VisitProjection(ProjectionExpression projectionExp
VisitInternal<SqlExpression>(projectionExpression.Expression).ResultExpression);
}

protected override Expression VisitQueryableSqlFunctionExpression(QueryableSqlFunctionExpression queryableFunctionExpression)
protected override Expression VisitQueryableFunctionExpression(QueryableFunctionExpression queryableFunctionExpression)
{
Check.NotNull(queryableFunctionExpression, nameof(queryableFunctionExpression));

return queryableFunctionExpression.Update(
VisitInternal<SqlFunctionExpression>(queryableFunctionExpression.SqlFunctionExpression).ResultExpression);
// See issue#20180
return queryableFunctionExpression;
}

protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpression)
Expand Down
18 changes: 16 additions & 2 deletions src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,25 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
return sqlFunctionExpression;
}

protected override Expression VisitQueryableSqlFunctionExpression(QueryableSqlFunctionExpression queryableFunctionExpression)
protected override Expression VisitQueryableFunctionExpression(QueryableFunctionExpression queryableFunctionExpression)
{
Visit(queryableFunctionExpression.SqlFunctionExpression);
Check.NotNull(queryableFunctionExpression, nameof(queryableFunctionExpression));

if (!string.IsNullOrEmpty(queryableFunctionExpression.Schema))
{
_relationalCommandBuilder
.Append(_sqlGenerationHelper.DelimitIdentifier(queryableFunctionExpression.Schema))
.Append(".");
}

_relationalCommandBuilder
.Append(_sqlGenerationHelper.DelimitIdentifier(queryableFunctionExpression.Name))
.Append("(");

GenerateList(queryableFunctionExpression.Arguments, e => Visit(e));

_relationalCommandBuilder
.Append(")")
.Append(AliasSeparator)
.Append(_sqlGenerationHelper.DelimitIdentifier(queryableFunctionExpression.Alias));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,14 @@ protected override Expression VisitExtension(Expression extensionExpression)
arguments.Add(sqlArgument);
}

// Default typeMapping is already applied
var translation = (SqlFunctionExpression)function.Translation?.Invoke(arguments)
?? _sqlExpressionFactory.Function(
function.Schema,
function.Name,
arguments,
nullable: true,
argumentsPropagateNullability: arguments.Select(a => false).ToList(),
function.MethodInfo.ReturnType);

// TODO: Allow translation to construct the table
var entityType = queryableFunctionQueryRootExpression.EntityType;
var alias = (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table.Name
?? entityType.ShortName()).Substring(0, 1).ToLower();

var translation = new QueryableFunctionExpression(function.Schema, function.Name, arguments, alias);
var queryExpression = _sqlExpressionFactory.Select(entityType, translation);

return CreateShapedQueryExpression(entityType, queryExpression);

default:
Expand Down
34 changes: 12 additions & 22 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -712,23 +712,7 @@ public virtual SqlFragmentExpression Fragment(string sql)
public virtual SqlConstantExpression Constant(object value, RelationalTypeMapping typeMapping = null)
=> new SqlConstantExpression(Expression.Constant(value), typeMapping);

public virtual SelectExpression Select(SqlExpression projection)
{
var selectExpression = new SelectExpression(
alias: null,
new List<ProjectionExpression>(),
new List<TableExpressionBase>(),
new List<SqlExpression>(),
new List<OrderingExpression>());

if (projection != null)
{
selectExpression.ReplaceProjectionMapping(
new Dictionary<ProjectionMember, Expression> { { new ProjectionMember(), projection } });
}

return selectExpression;
}
public virtual SelectExpression Select(SqlExpression projection) => new SelectExpression(projection);

public virtual SelectExpression Select(IEntityType entityType)
{
Expand All @@ -740,20 +724,26 @@ public virtual SelectExpression Select(IEntityType entityType)
return selectExpression;
}

public virtual SelectExpression Select(IEntityType entityType, string sql, Expression sqlArguments)
public virtual SelectExpression Select(IEntityType entityType, TableExpressionBase tableExpressionBase)
{
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(sql, nameof(sql));
Check.NotNull(tableExpressionBase, nameof(tableExpressionBase));

var selectExpression = new SelectExpression(entityType, sql, sqlArguments);
var selectExpression = new SelectExpression(entityType, tableExpressionBase);
AddConditions(selectExpression, entityType);

return selectExpression;
}

public virtual SelectExpression Select(IEntityType entityType, SqlFunctionExpression expression)
public virtual SelectExpression Select(IEntityType entityType, string sql, Expression sqlArguments)
{
var selectExpression = new SelectExpression(entityType, expression);
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(sql, nameof(sql));

var tableExpression = new FromSqlExpression(sql, sqlArguments,
(entityType.GetViewOrTableMappings().SingleOrDefault()?.Table.Name
?? entityType.ShortName()).Substring(0, 1).ToLower());
var selectExpression = new SelectExpression(entityType, tableExpression);
AddConditions(selectExpression, entityType);

return selectExpression;
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore.Relational/Query/SqlExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
case ProjectionExpression projectionExpression:
return VisitProjection(projectionExpression);

case QueryableSqlFunctionExpression queryableFunctionExpression:
return VisitQueryableSqlFunctionExpression(queryableFunctionExpression);
case QueryableFunctionExpression queryableFunctionExpression:
return VisitQueryableFunctionExpression(queryableFunctionExpression);

case RowNumberExpression rowNumberExpression:
return VisitRowNumber(rowNumberExpression);
Expand Down Expand Up @@ -120,7 +120,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
protected abstract Expression VisitOrdering([NotNull] OrderingExpression orderingExpression);
protected abstract Expression VisitOuterApply([NotNull] OuterApplyExpression outerApplyExpression);
protected abstract Expression VisitProjection([NotNull] ProjectionExpression projectionExpression);
protected abstract Expression VisitQueryableSqlFunctionExpression([NotNull] QueryableSqlFunctionExpression queryableFunctionExpression);
protected abstract Expression VisitQueryableFunctionExpression([NotNull] QueryableFunctionExpression queryableFunctionExpression);
protected abstract Expression VisitRowNumber([NotNull] RowNumberExpression rowNumberExpression);
protected abstract Expression VisitScalarSubquery([NotNull] ScalarSubqueryExpression scalarSubqueryExpression);
protected abstract Expression VisitSelect([NotNull] SelectExpression selectExpression);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions
{
/// <summary>
/// Represents a SQL Table Valued Fuction in the sql generation tree.
/// </summary>
public class QueryableFunctionExpression : TableExpressionBase
{
public QueryableFunctionExpression(
[CanBeNull] string schema, [NotNull] string name, [NotNull] IReadOnlyList<SqlExpression> arguments, [NotNull] string alias)
: base(alias)
{
Check.NullButNotEmpty(schema, nameof(schema));
Check.NotEmpty(name, nameof(name));
Check.NotNull(arguments, nameof(arguments));

Schema = schema;
Name = name;
Arguments = arguments;
}

public virtual string Schema { get; }
public virtual string Name { get; }
public virtual IReadOnlyList<SqlExpression> Arguments { get; }

protected override Expression VisitChildren(ExpressionVisitor visitor)
{
Check.NotNull(visitor, nameof(visitor));

var changed = false;
var arguments = new SqlExpression[Arguments.Count];
for (var i = 0; i < arguments.Length; i++)
{
arguments[i] = (SqlExpression)visitor.Visit(Arguments[i]);
changed |= arguments[i] != Arguments[i];
}

return changed
? new QueryableFunctionExpression(Schema, Name, arguments, Alias)
: this;
}

public virtual QueryableFunctionExpression Update([NotNull] IReadOnlyList<SqlExpression> arguments)
{
Check.NotNull(arguments, nameof(arguments));

return !arguments.SequenceEqual(Arguments)
? new QueryableFunctionExpression(Schema, Name, arguments, Alias)
: this;
}

public override void Print(ExpressionPrinter expressionPrinter)
{
if (!string.IsNullOrEmpty(Schema))
{
expressionPrinter.Append(Schema).Append(".");
}

expressionPrinter.Append(Name);
expressionPrinter.Append("(");
expressionPrinter.VisitCollection(Arguments);
expressionPrinter.Append(") AS ");
expressionPrinter.Append(Alias);
}

public override bool Equals(object obj)
=> obj != null
&& (ReferenceEquals(this, obj)
|| obj is QueryableFunctionExpression queryableExpression
&& Equals(queryableExpression));

private bool Equals(QueryableFunctionExpression queryableExpression)
=> base.Equals(queryableExpression)
&& string.Equals(Name, queryableExpression.Name)
&& string.Equals(Schema, queryableExpression.Schema)
&& Arguments.SequenceEqual(queryableExpression.Arguments);

public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(base.GetHashCode());
hash.Add(Schema);
hash.Add(Name);
for (var i = 0; i < Arguments.Count; i++)
{
hash.Add(Arguments[i]);
}

return hash.ToHashCode();
}
}
}
28 changes: 10 additions & 18 deletions src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void ApplyTags([NotNull] ISet<string> tags)
Tags = tags;
}

internal SelectExpression(
private SelectExpression(
string alias,
List<ProjectionExpression> projections,
List<TableExpressionBase> tables,
Expand All @@ -65,29 +65,21 @@ internal SelectExpression(
_orderings = orderings;
}

internal SelectExpression(IEntityType entityType)
: this(entityType, new TableExpression(entityType.GetViewOrTableMappings().Single().Table))
{
}

internal SelectExpression(IEntityType entityType, string sql, Expression arguments)
: this(
entityType, new FromSqlExpression(
sql,
arguments,
(entityType.GetViewOrTableMappings().SingleOrDefault()?.Table.Name
?? entityType.ShortName()).Substring(0, 1).ToLower()))
internal SelectExpression(SqlExpression projection)
:base(null)
{
if (projection != null)
{
_projectionMapping[new ProjectionMember()] = projection;
}
}

internal SelectExpression(IEntityType entityType, SqlFunctionExpression expression)
: this(
entityType, new QueryableSqlFunctionExpression(expression,
entityType.GetTableName().ToLower().Substring(0, 1)))
internal SelectExpression(IEntityType entityType)
: this(entityType, new TableExpression(entityType.GetViewOrTableMappings().Single().Table))
{
}

private SelectExpression(IEntityType entityType, TableExpressionBase tableExpression)
internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpression)
: base(null)
{
_tables.Add(tableExpression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,11 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
return ApplyConversion(newFunction, condition);
}

protected override Expression VisitQueryableSqlFunctionExpression(QueryableSqlFunctionExpression queryableFunctionExpression)
protected override Expression VisitQueryableFunctionExpression(QueryableFunctionExpression queryableFunctionExpression)
{
Check.NotNull(queryableFunctionExpression, nameof(queryableFunctionExpression));

// TODO: See issue#20180
return queryableFunctionExpression;
}

Expand Down
Loading