Skip to content

Commit 3108ce3

Browse files
committed
WIP
1 parent 919a645 commit 3108ce3

31 files changed

+6602
-7
lines changed

src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
6969
{ typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7070
{ typeof(IMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7171
{ typeof(IAggregateMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
72+
{ typeof(IWindowAggregateMethodCallTranslator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7273
{ typeof(IMemberTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7374
{ typeof(ISqlExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7475
{ typeof(IRelationalQueryStringFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
@@ -96,7 +97,8 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
9697
typeof(IAggregateMethodCallTranslatorPlugin),
9798
new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true)
9899
},
99-
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }
100+
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
101+
{ typeof(IWindowBuilderExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }
100102
};
101103

102104
/// <summary>
@@ -179,6 +181,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
179181
TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory, RelationalQueryableMethodTranslatingExpressionVisitorFactory>();
180182
TryAdd<IMethodCallTranslatorProvider, RelationalMethodCallTranslatorProvider>();
181183
TryAdd<IAggregateMethodCallTranslatorProvider, RelationalAggregateMethodCallTranslatorProvider>();
184+
TryAdd<IWindowAggregateMethodCallTranslator, RelationalWindowAggregateMethodTranslator>();
182185
TryAdd<IMemberTranslatorProvider, RelationalMemberTranslatorProvider>();
183186
TryAdd<IQueryTranslationPostprocessorFactory, RelationalQueryTranslationPostprocessorFactory>();
184187
TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, RelationalSqlTranslatingExpressionVisitorFactory>();
@@ -192,6 +195,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
192195
TryAdd<ILiftableConstantFactory>(p => p.GetRequiredService<IRelationalLiftableConstantFactory>());
193196
TryAdd<IRelationalLiftableConstantFactory, RelationalLiftableConstantFactory>();
194197
TryAdd<ILiftableConstantProcessor, RelationalLiftableConstantProcessor>();
198+
TryAdd<IWindowBuilderExpressionFactory, WindowBuilderExpressionFactory>();
195199

196200
ServiceCollectionMap.GetInfrastructure()
197201
.AddDependencySingleton<RelationalSqlGenerationHelperDependencies>()
@@ -229,7 +233,8 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
229233
.AddDependencyScoped<RelationalDatabaseDependencies>()
230234
.AddDependencyScoped<RelationalQueryContextDependencies>()
231235
.AddDependencyScoped<RelationalQueryCompilationContextDependencies>()
232-
.AddDependencyScoped<RelationalAdHocMapperDependencies>();
236+
.AddDependencyScoped<RelationalAdHocMapperDependencies>()
237+
.AddDependencyScoped<WindowBuilderExpressionFactory>();
233238

234239
return base.TryAddCoreServices();
235240
}

src/EFCore.Relational/Query/ISqlExpressionFactory.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,4 +437,58 @@ SqlExpression NiladicFunction(
437437
/// <param name="sql">A string token to print in SQL tree.</param>
438438
/// <returns>An expression representing a SQL token.</returns>
439439
SqlExpression Fragment(string sql);
440+
441+
/// <summary>
442+
/// Attempts to creates a new expression that returns the smallest value from a list of expressions, e.g. an invocation of the
443+
/// <c>LEAST</c> SQL function.
444+
/// </summary>
445+
/// <param name="expressions">An entity type to project.</param>
446+
/// <param name="resultType">The result CLR type for the returned expression.</param>
447+
/// <param name="leastExpression">The expression which computes the smallest value.</param>
448+
/// <returns><see langword="true" /> if the expression could be created, <see langword="false" /> otherwise.</returns>
449+
bool TryCreateLeast(
450+
IReadOnlyList<SqlExpression> expressions,
451+
Type resultType,
452+
[NotNullWhen(true)] out SqlExpression? leastExpression);
453+
454+
/// <summary>
455+
/// Attempts to creates a new expression that returns the greatest value from a list of expressions, e.g. an invocation of the
456+
/// <c>GREATEST</c> SQL function.
457+
/// </summary>
458+
/// <param name="expressions">An entity type to project.</param>
459+
/// <param name="resultType">The result CLR type for the returned expression.</param>
460+
/// <param name="greatestExpression">The expression which computes the greatest value.</param>
461+
/// <returns><see langword="true" /> if the expression could be created, <see langword="false" /> otherwise.</returns>
462+
bool TryCreateGreatest(
463+
IReadOnlyList<SqlExpression> expressions,
464+
Type resultType,
465+
[NotNullWhen(true)] out SqlExpression? greatestExpression);
466+
467+
/// <summary>
468+
/// todo
469+
/// </summary>
470+
/// <param name="partitions">todo</param>
471+
/// <returns>todo</returns>
472+
WindowPartitionExpression PartitionBy(IEnumerable<SqlExpression> partitions);
473+
474+
/// <summary>
475+
/// todo
476+
/// </summary>
477+
/// <param name="aggregate">todo</param>
478+
/// <param name="partition">todo</param>
479+
/// <param name="orderings">todo</param>
480+
/// <param name="frame">todo</param>
481+
/// <returns>todo</returns>
482+
WindowOverExpression Over(SqlFunctionExpression aggregate, WindowPartitionExpression? partition, IReadOnlyList<OrderingExpression> orderings,
483+
WindowFrameExpression? frame);
484+
485+
/// <summary>
486+
/// todo
487+
/// </summary>
488+
/// <param name="method">todo</param>
489+
/// <param name="preceding">todo</param>
490+
/// <param name="following">todo</param>
491+
/// <param name="exclude">todo</param>
492+
/// <returns>todo</returns>
493+
WindowFrameExpression WindowFrame(MethodInfo method, SqlExpression? preceding, SqlExpression? following, SqlExpression? exclude);
440494
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
10+
11+
namespace Microsoft.EntityFrameworkCore.Query;
12+
13+
/// <summary>
14+
/// todo
15+
/// </summary>
16+
public interface IWindowAggregateMethodCallTranslator
17+
{
18+
/// <summary>
19+
/// todo
20+
/// </summary>
21+
/// <param name="method">todo</param>
22+
/// <param name="arguments">todo</param>
23+
/// <param name="logger">todo</param>
24+
/// <returns>todo</returns>
25+
SqlExpression? Translate(
26+
MethodInfo method,
27+
IReadOnlyList<SqlExpression> arguments,
28+
IDiagnosticsLogger<DbLoggerCategory.Query> logger);
29+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace Microsoft.EntityFrameworkCore.Query;
11+
12+
/// <summary>
13+
/// todo
14+
/// </summary>
15+
public interface IWindowAggregateMethodCallTranslatorPlugin
16+
{
17+
/// <summary>
18+
/// Gets the method call translators.
19+
/// </summary>
20+
IEnumerable<IWindowAggregateMethodCallTranslator> Translators { get; }
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace Microsoft.EntityFrameworkCore.Query;
11+
12+
/// <summary>
13+
/// todo
14+
/// </summary>
15+
public interface IWindowBuilderExpressionFactory
16+
{
17+
/// <summary>
18+
/// todo
19+
/// </summary>
20+
/// <returns>todo</returns>
21+
RelationalWindowBuilderExpression CreateWindowBuilder();
22+
}

src/EFCore.Relational/Query/QuerySqlGenerator.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Reflection.Metadata;
45
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
56
using Microsoft.EntityFrameworkCore.Storage.Internal;
67

@@ -1763,4 +1764,97 @@ protected virtual bool TryGetOperatorInfo(SqlExpression expression, out int prec
17631764
(precedence, isAssociative) = (default, default);
17641765
return false;
17651766
}
1767+
1768+
/// <inheritdoc />
1769+
protected override Expression VisitOver(WindowOverExpression windowOverExpression)
1770+
{
1771+
Visit(windowOverExpression.Aggregate);
1772+
1773+
_relationalCommandBuilder.Append(" OVER (");
1774+
1775+
if(windowOverExpression.Partition != null)
1776+
VisitWindowPartition(windowOverExpression.Partition);
1777+
1778+
if (windowOverExpression.Ordering.Count > 0)
1779+
{
1780+
_relationalCommandBuilder.Append(" ORDER BY ");
1781+
1782+
GenerateList(windowOverExpression.Ordering, e => Visit(e));
1783+
}
1784+
1785+
if (windowOverExpression.WindowFrame != null)
1786+
VisitWindowFrame(windowOverExpression.WindowFrame);
1787+
1788+
_relationalCommandBuilder.Append(")");
1789+
1790+
return windowOverExpression;
1791+
}
1792+
1793+
/// <inheritdoc />
1794+
protected override Expression VisitWindowPartition(WindowPartitionExpression partitionExpression)
1795+
{
1796+
_relationalCommandBuilder.Append("PARTITION BY ");
1797+
1798+
GenerateList(partitionExpression.Partitions, e => Visit(e), sql => sql.Append(", "));
1799+
1800+
return partitionExpression;
1801+
}
1802+
1803+
/// <inheritdoc />
1804+
protected override Expression VisitWindowFrame(WindowFrameExpression windowsFrameExpression)
1805+
{
1806+
//todo - sqllite groups override test
1807+
1808+
_relationalCommandBuilder.Append($" {windowsFrameExpression.FrameName} ");
1809+
1810+
if(windowsFrameExpression.Following != null)
1811+
_relationalCommandBuilder.Append($"BETWEEN ");
1812+
1813+
if (windowsFrameExpression.Preceding is SqlConstantExpression preceedingExpression && preceedingExpression?.Type == typeof(RowsPreceding))
1814+
{
1815+
_relationalCommandBuilder.Append((RowsPreceding)preceedingExpression.Value! == RowsPreceding.CurrentRow
1816+
? "CURRENT ROW"
1817+
: "UNBOUNDED PRECEDING");
1818+
}
1819+
else
1820+
{
1821+
Visit(windowsFrameExpression.Preceding);
1822+
1823+
_relationalCommandBuilder.Append($" PRECEDING");
1824+
}
1825+
1826+
if(windowsFrameExpression.Following != null)
1827+
{
1828+
_relationalCommandBuilder.Append($" AND ");
1829+
1830+
if (windowsFrameExpression.Following is SqlConstantExpression followingExpression && followingExpression?.Type == typeof(RowsFollowing))
1831+
{
1832+
_relationalCommandBuilder.Append((RowsPreceding)followingExpression.Value! == RowsPreceding.CurrentRow
1833+
? "CURRENT ROW"
1834+
: "UNBOUNDED FOLLOWING");
1835+
}
1836+
else
1837+
{
1838+
Visit(windowsFrameExpression.Following);
1839+
1840+
_relationalCommandBuilder.Append($" FOLLOWING");
1841+
}
1842+
}
1843+
1844+
if (windowsFrameExpression.Exclude is SqlConstantExpression excludeExpression && excludeExpression?.Type == typeof(FrameExclude))
1845+
{
1846+
_relationalCommandBuilder.Append($" EXCLUDE ");
1847+
1848+
_relationalCommandBuilder.Append((FrameExclude)excludeExpression.Value! switch
1849+
{
1850+
FrameExclude.NoOthers => "NO OTHERS",
1851+
FrameExclude.CurrentRow => "CURRENT ROW",
1852+
FrameExclude.Group => "GROUP",
1853+
FrameExclude.Ties => "TIES",
1854+
_ => throw new ArgumentOutOfRangeException()
1855+
});
1856+
}
1857+
1858+
return windowsFrameExpression;
1859+
}
17661860
}

src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
4343
return false;
4444
}
4545

46-
if (method.DeclaringType == typeof(RelationalDbFunctionsExtensions))
46+
if (method.DeclaringType == typeof(RelationalDbFunctionsExtensions) || method.DeclaringType == typeof(WindowFunctionsExtensions))
4747
{
4848
return false;
4949
}
50+
5051
}
5152

5253
return base.IsEvaluatableExpression(expression, model);

0 commit comments

Comments
 (0)