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

Add support for database window functions - WIP #34258

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
{ typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IAggregateMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IWindowAggregateMethodCallTranslator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IMemberTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(ISqlExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalQueryStringFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
Expand Down Expand Up @@ -96,7 +97,8 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
typeof(IAggregateMethodCallTranslatorPlugin),
new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true)
},
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IWindowBuilderExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }
};

/// <summary>
Expand Down Expand Up @@ -179,6 +181,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory, RelationalQueryableMethodTranslatingExpressionVisitorFactory>();
TryAdd<IMethodCallTranslatorProvider, RelationalMethodCallTranslatorProvider>();
TryAdd<IAggregateMethodCallTranslatorProvider, RelationalAggregateMethodCallTranslatorProvider>();
TryAdd<IWindowAggregateMethodCallTranslator, RelationalWindowAggregateMethodTranslator>();
TryAdd<IMemberTranslatorProvider, RelationalMemberTranslatorProvider>();
TryAdd<IQueryTranslationPostprocessorFactory, RelationalQueryTranslationPostprocessorFactory>();
TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, RelationalSqlTranslatingExpressionVisitorFactory>();
Expand All @@ -192,6 +195,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<ILiftableConstantFactory>(p => p.GetRequiredService<IRelationalLiftableConstantFactory>());
TryAdd<IRelationalLiftableConstantFactory, RelationalLiftableConstantFactory>();
TryAdd<ILiftableConstantProcessor, RelationalLiftableConstantProcessor>();
TryAdd<IWindowBuilderExpressionFactory, WindowBuilderExpressionFactory>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<RelationalSqlGenerationHelperDependencies>()
Expand Down Expand Up @@ -229,7 +233,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
.AddDependencyScoped<RelationalDatabaseDependencies>()
.AddDependencyScoped<RelationalQueryContextDependencies>()
.AddDependencyScoped<RelationalQueryCompilationContextDependencies>()
.AddDependencyScoped<RelationalAdHocMapperDependencies>();
.AddDependencyScoped<RelationalAdHocMapperDependencies>()
.AddDependencyScoped<WindowBuilderExpressionFactory>()
.AddDependencyScoped<RelationalWindowAggregateMethodTranslatorDependencies>();

return base.TryAddCoreServices();
}
Expand Down
54 changes: 54 additions & 0 deletions src/EFCore.Relational/Query/ISqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,58 @@ SqlExpression NiladicFunction(
/// <param name="sql">A string token to print in SQL tree.</param>
/// <returns>An expression representing a SQL token.</returns>
SqlExpression Fragment(string sql);

/// <summary>
/// Attempts to creates a new expression that returns the smallest value from a list of expressions, e.g. an invocation of the
/// <c>LEAST</c> SQL function.
/// </summary>
/// <param name="expressions">An entity type to project.</param>
/// <param name="resultType">The result CLR type for the returned expression.</param>
/// <param name="leastExpression">The expression which computes the smallest value.</param>
/// <returns><see langword="true" /> if the expression could be created, <see langword="false" /> otherwise.</returns>
bool TryCreateLeast(
IReadOnlyList<SqlExpression> expressions,
Type resultType,
[NotNullWhen(true)] out SqlExpression? leastExpression);

/// <summary>
/// Attempts to creates a new expression that returns the greatest value from a list of expressions, e.g. an invocation of the
/// <c>GREATEST</c> SQL function.
/// </summary>
/// <param name="expressions">An entity type to project.</param>
/// <param name="resultType">The result CLR type for the returned expression.</param>
/// <param name="greatestExpression">The expression which computes the greatest value.</param>
/// <returns><see langword="true" /> if the expression could be created, <see langword="false" /> otherwise.</returns>
bool TryCreateGreatest(
IReadOnlyList<SqlExpression> expressions,
Type resultType,
[NotNullWhen(true)] out SqlExpression? greatestExpression);

/// <summary>
/// todo
/// </summary>
/// <param name="partitions">todo</param>
/// <returns>todo</returns>
WindowPartitionExpression PartitionBy(IEnumerable<SqlExpression> partitions);

/// <summary>
/// todo
/// </summary>
/// <param name="aggregate">todo</param>
/// <param name="partition">todo</param>
/// <param name="orderings">todo</param>
/// <param name="frame">todo</param>
/// <returns>todo</returns>
WindowOverExpression Over(SqlFunctionExpression aggregate, WindowPartitionExpression? partition, IReadOnlyList<OrderingExpression> orderings,
WindowFrameExpression? frame);

/// <summary>
/// todo
/// </summary>
/// <param name="method">todo</param>
/// <param name="preceding">todo</param>
/// <param name="following">todo</param>
/// <param name="exclude">todo</param>
/// <returns>todo</returns>
WindowFrameExpression WindowFrame(MethodInfo method, SqlExpression? preceding, SqlExpression? following, SqlExpression? exclude);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query;

/// <summary>
/// todo
/// </summary>
public interface IWindowAggregateMethodCallTranslator
{
/// <summary>
/// todo
/// </summary>
/// <param name="method">todo</param>
/// <param name="arguments">todo</param>
/// <param name="logger">todo</param>
/// <returns>todo</returns>
SqlExpression? Translate(
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.EntityFrameworkCore.Query;

/// <summary>
/// todo
/// </summary>
public interface IWindowAggregateMethodCallTranslatorPlugin
{
/// <summary>
/// Gets the method call translators.
/// </summary>
IEnumerable<IWindowAggregateMethodCallTranslator> Translators { get; }
}
22 changes: 22 additions & 0 deletions src/EFCore.Relational/Query/IWindowBuilderExpressionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.EntityFrameworkCore.Query;

/// <summary>
/// todo
/// </summary>
public interface IWindowBuilderExpressionFactory
{
/// <summary>
/// todo
/// </summary>
/// <returns>todo</returns>
RelationalWindowBuilderExpression CreateWindowBuilder();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.EntityFrameworkCore.Query.Internal;

/// <summary>
/// todo
/// </summary>
public class WindowBuilderExpressionFactory : IWindowBuilderExpressionFactory
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// todo
/// </summary>
/// <param name="sqlExpressionFactory">todo</param>
public WindowBuilderExpressionFactory(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <summary>
/// todo
/// </summary>
/// <returns>todo</returns>
public RelationalWindowBuilderExpression CreateWindowBuilder()
{
return new RelationalWindowBuilderExpression(_sqlExpressionFactory);
}
}
94 changes: 94 additions & 0 deletions src/EFCore.Relational/Query/QuerySqlGenerator.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.Reflection.Metadata;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage.Internal;

Expand Down Expand Up @@ -1763,4 +1764,97 @@ protected virtual bool TryGetOperatorInfo(SqlExpression expression, out int prec
(precedence, isAssociative) = (default, default);
return false;
}

/// <inheritdoc />
protected override Expression VisitOver(WindowOverExpression windowOverExpression)
{
Visit(windowOverExpression.Aggregate);

_relationalCommandBuilder.Append(" OVER (");

if(windowOverExpression.Partition != null)
VisitWindowPartition(windowOverExpression.Partition);

if (windowOverExpression.Ordering.Count > 0)
{
_relationalCommandBuilder.Append(" ORDER BY ");

GenerateList(windowOverExpression.Ordering, e => Visit(e));
}

if (windowOverExpression.WindowFrame != null)
VisitWindowFrame(windowOverExpression.WindowFrame);

_relationalCommandBuilder.Append(")");

return windowOverExpression;
}

/// <inheritdoc />
protected override Expression VisitWindowPartition(WindowPartitionExpression partitionExpression)
{
_relationalCommandBuilder.Append("PARTITION BY ");

GenerateList(partitionExpression.Partitions, e => Visit(e), sql => sql.Append(", "));

return partitionExpression;
}

/// <inheritdoc />
protected override Expression VisitWindowFrame(WindowFrameExpression windowsFrameExpression)
{
//todo - sqllite groups override test

_relationalCommandBuilder.Append($" {windowsFrameExpression.FrameName} ");

if(windowsFrameExpression.Following != null)
_relationalCommandBuilder.Append($"BETWEEN ");

if (windowsFrameExpression.Preceding is SqlConstantExpression preceedingExpression && preceedingExpression?.Type == typeof(RowsPreceding))
{
_relationalCommandBuilder.Append((RowsPreceding)preceedingExpression.Value! == RowsPreceding.CurrentRow
? "CURRENT ROW"
: "UNBOUNDED PRECEDING");
}
else
{
Visit(windowsFrameExpression.Preceding);

_relationalCommandBuilder.Append($" PRECEDING");
}

if(windowsFrameExpression.Following != null)
{
_relationalCommandBuilder.Append($" AND ");

if (windowsFrameExpression.Following is SqlConstantExpression followingExpression && followingExpression?.Type == typeof(RowsFollowing))
{
_relationalCommandBuilder.Append((RowsPreceding)followingExpression.Value! == RowsPreceding.CurrentRow
? "CURRENT ROW"
: "UNBOUNDED FOLLOWING");
}
else
{
Visit(windowsFrameExpression.Following);

_relationalCommandBuilder.Append($" FOLLOWING");
}
}

if (windowsFrameExpression.Exclude is SqlConstantExpression excludeExpression && excludeExpression?.Type == typeof(FrameExclude))
{
_relationalCommandBuilder.Append($" EXCLUDE ");

_relationalCommandBuilder.Append((FrameExclude)excludeExpression.Value! switch
{
FrameExclude.NoOthers => "NO OTHERS",
FrameExclude.CurrentRow => "CURRENT ROW",
FrameExclude.Group => "GROUP",
FrameExclude.Ties => "TIES",
_ => throw new ArgumentOutOfRangeException()
});
}

return windowsFrameExpression;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
return false;
}

if (method.DeclaringType == typeof(RelationalDbFunctionsExtensions))
if (method.DeclaringType == typeof(RelationalDbFunctionsExtensions) || method.DeclaringType == typeof(WindowFunctionsExtensions))
{
return false;
}

}

return base.IsEvaluatableExpression(expression, model);
Expand Down
Loading
Loading