Skip to content

Commit

Permalink
Merge branch 'releases/7.x.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelGerr committed Feb 4, 2024
2 parents 6b5ba6a + ac4a3b2 commit 29ebadf
Show file tree
Hide file tree
Showing 15 changed files with 943 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ internal RelationalDbFunctionsTranslator(ISqlExpressionFactory sqlExpressionFact

switch (method.Name)
{
case nameof(RelationalDbFunctionsExtensions.PartitionBy):
{
return new WindowFunctionPartitionByExpression(arguments.Skip(1).ToList());
}
case nameof(RelationalDbFunctionsExtensions.OrderBy):
{
var orderBy = arguments.Skip(1).Select(e => new OrderingExpression(_sqlExpressionFactory.ApplyDefaultTypeMapping(e), true)).ToList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace Thinktecture.EntityFrameworkCore.Query.SqlExpressions;

/// <summary>
/// Represents PARTITION BY.
/// </summary>
public class WindowFunctionPartitionByExpression : SqlExpression, INotNullableSqlExpression
{
/// <summary>
/// Partition by expressions.
/// </summary>
public IReadOnlyList<SqlExpression> PartitionBy { get; }

/// <inheritdoc />
public WindowFunctionPartitionByExpression(IReadOnlyList<SqlExpression> partitionBy)
: base(typeof(WindowFunctionPartitionByClause), RelationalTypeMapping.NullMapping)
{
PartitionBy = partitionBy ?? throw new ArgumentNullException(nameof(partitionBy));
}

/// <inheritdoc />
protected override Expression Accept(ExpressionVisitor visitor)
{
if (visitor is QuerySqlGenerator)
throw new NotSupportedException("The window function contains some expressions not supported by the Entity Framework.");

return base.Accept(visitor);
}

/// <inheritdoc />
protected override Expression VisitChildren(ExpressionVisitor visitor)
{
var visited = visitor.VisitExpressions(PartitionBy);

return ReferenceEquals(visited, PartitionBy) ? this : new WindowFunctionPartitionByExpression(visited);
}

/// <inheritdoc />
protected override void Print(ExpressionPrinter expressionPrinter)
{
ArgumentNullException.ThrowIfNull(expressionPrinter);

expressionPrinter.VisitCollection(PartitionBy);
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj != null && (ReferenceEquals(this, obj) || Equals(obj as WindowFunctionPartitionByExpression));
}

private bool Equals(WindowFunctionPartitionByExpression? expression)
{
return base.Equals(expression) && PartitionBy.SequenceEqual(expression.PartitionBy);
}

/// <inheritdoc />
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(base.GetHashCode());

for (var i = 0; i < PartitionBy.Count; i++)
{
hash.Add(PartitionBy[i]);
}

return hash.ToHashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Thinktecture.EntityFrameworkCore;

/// <summary>
/// Helper class to attach extension methods to.
/// </summary>
public sealed class WindowFunctionPartitionByClause
{
private WindowFunctionPartitionByClause()
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public static long RowNumber<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
}

/// <summary>
/// Definition of the ORDER BY clause of a the ROW_NUMBER expression.
/// Definition of the ORDER BY clause of a the window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
Expand All @@ -228,7 +228,7 @@ public static WindowFunctionOrderByClause OrderBy<T>(this DbFunctions _, T colum
}

/// <summary>
/// Definition of the ORDER BY clause of a the ROW_NUMBER expression.
/// Definition of the ORDER BY clause of a the window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
Expand All @@ -240,7 +240,7 @@ public static WindowFunctionOrderByClause OrderByDescending<T>(this DbFunctions
}

/// <summary>
/// Definition of the ORDER BY clause of a the ROW_NUMBER expression.
/// Definition of the ORDER BY clause of a the window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
Expand All @@ -252,7 +252,7 @@ public static WindowFunctionOrderByClause ThenBy<T>(this WindowFunctionOrderByCl
}

/// <summary>
/// Definition of the ORDER BY clause of a the ROW_NUMBER expression.
/// Definition of the ORDER BY clause of a the window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
Expand All @@ -263,5 +263,65 @@ public static WindowFunctionOrderByClause ThenByDescending<T>(this WindowFunctio
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}

/// <summary>
/// Definition of the PARTITION BY clause of a window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
/// </remarks>
/// <exception cref="InvalidOperationException">Is thrown if executed in-memory.</exception>
public static WindowFunctionPartitionByClause PartitionBy<T1>(this DbFunctions _, T1 column1)
{
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}

/// <summary>
/// Definition of the PARTITION BY clause of a window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
/// </remarks>
/// <exception cref="InvalidOperationException">Is thrown if executed in-memory.</exception>
public static WindowFunctionPartitionByClause PartitionBy<T1, T2>(this DbFunctions _, T1 column1, T2 column2)
{
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}

/// <summary>
/// Definition of the PARTITION BY clause of a window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
/// </remarks>
/// <exception cref="InvalidOperationException">Is thrown if executed in-memory.</exception>
public static WindowFunctionPartitionByClause PartitionBy<T1, T2, T3>(this DbFunctions _, T1 column1, T2 column2, T3 column3)
{
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}

/// <summary>
/// Definition of the PARTITION BY clause of a window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
/// </remarks>
/// <exception cref="InvalidOperationException">Is thrown if executed in-memory.</exception>
public static WindowFunctionPartitionByClause PartitionBy<T1, T2, T3, T4>(this DbFunctions _, T1 column1, T2 column2, T3 column3, T4 column4)
{
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}

/// <summary>
/// Definition of the PARTITION BY clause of a window function.
/// </summary>
/// <remarks>
/// This method is for use with Entity Framework Core only and has no in-memory implementation.
/// </remarks>
/// <exception cref="InvalidOperationException">Is thrown if executed in-memory.</exception>
public static WindowFunctionPartitionByClause PartitionBy<T1, T2, T3, T4, T5>(this DbFunctions _, T1 column1, T2 column2, T3 column3, T4 column4, T5 column5)
{
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}

// ReSharper restore UnusedParameter.Global
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

// ReSharper disable once CheckNamespace
namespace Thinktecture;
Expand All @@ -20,19 +21,27 @@ public static class RelationalExpressionVisitorExtensions
public static IReadOnlyList<T> VisitExpressions<T>(this ExpressionVisitor visitor, IReadOnlyList<T> expressions)
where T : Expression
{
ArgumentNullException.ThrowIfNull(visitor);
ArgumentNullException.ThrowIfNull(expressions);
T[]? visitedExpression = null;

var visitedExpressions = new List<T>();
var hasChanges = false;

foreach (var expression in expressions)
for (var i = 0; i < expressions.Count; i++)
{
var visitedExpression = (T)visitor.Visit(expression);
visitedExpressions.Add(visitedExpression);
hasChanges |= !ReferenceEquals(visitedExpression, expression);
var expression = expressions[i];
var visited = (T)visitor.Visit(expression);

if (visited != expression && visitedExpression is null)
{
visitedExpression = new T[expressions.Count];

for (var j = 0; j < i; j++)
{
visitedExpression[j] = expressions[j];
}
}

if (visitedExpression is not null)
visitedExpression[i] = visited;
}

return hasChanges ? visitedExpressions.AsReadOnly() : expressions;
return visitedExpression ?? expressions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System.Text.RegularExpressions;

namespace Thinktecture;

/// <summary>
/// Represents a window function name.
/// </summary>
public abstract partial class WindowFunction
{
private const string _REGEX_PATTERN = "^[A-Z_]+$";
private const RegexOptions _REGEX_OPTIONS = RegexOptions.IgnoreCase | RegexOptions.Compiled;

[GeneratedRegex(_REGEX_PATTERN, _REGEX_OPTIONS)]
private static partial Regex ValidateName();

/// <summary>
/// Name of the function.
/// </summary>
public string Name { get; }

/// <summary>
/// Return type of the function.
/// </summary>
public Type ReturnType { get; }

/// <summary>
/// Indication whether to use '*' when no arguments are provided.
/// </summary>
public bool UseStarWhenNoArguments { get; }

/// <summary>
/// Initializes a new instance of <see cref="WindowFunction"/>.
/// </summary>
/// <param name="name">The name of the window function.</param>
/// <param name="returnType">Return type of the function.</param>
/// <param name="useStarWhenNoArguments">Indication whether to use '*' when no arguments are provided.</param>
protected WindowFunction(string name, Type returnType, bool useStarWhenNoArguments)
{
Name = EnsureValidName(name);
ReturnType = returnType ?? throw new ArgumentNullException(nameof(returnType));
UseStarWhenNoArguments = useStarWhenNoArguments;
}

private static string EnsureValidName(string name)
{
if (String.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name must not be empty.");

name = name.Trim();

if (!ValidateName().IsMatch(name))
throw new ArgumentException("The name must consist of characters A-Z and may contain an underscore.");

return name;
}

/// <summary>
/// Deconstruction of the window function.
/// </summary>
/// <param name="name">The name of the function.</param>
/// <param name="returnType">The return type of the function.</param>
/// <param name="useStarWhenNoArguments">Indication whether to use '*' when no arguments are provided.</param>
public void Deconstruct(out string name, out Type returnType, out bool useStarWhenNoArguments)
{
name = Name;
returnType = ReturnType;
useStarWhenNoArguments = UseStarWhenNoArguments;
}
}

/// <summary>
/// Represents a window function name.
/// </summary>
public sealed class WindowFunction<TResult> : WindowFunction, IEquatable<WindowFunction<TResult>>
{
/// <summary>
/// Initializes a new instance of <see cref="WindowFunction{TResult}"/>.
/// </summary>
/// <param name="name">The name of the window function</param>
/// <param name="useStarWhenNoArguments">Indication whether to use '*' when no arguments are provided.</param>
public WindowFunction(
string name,
bool useStarWhenNoArguments = false)
: base(name, typeof(TResult), useStarWhenNoArguments)
{
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
return Equals(obj as WindowFunction<TResult>);
}

/// <inheritdoc />
public bool Equals(WindowFunction<TResult>? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;

return Name == other.Name;
}

/// <inheritdoc />
public override int GetHashCode()
{
return Name.GetHashCode();
}

/// <inheritdoc />
public override string ToString()
{
return Name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ public void ApplyServices(IServiceCollection services)

if (_relationalOptions.AddSchemaRespectingComponents)
services.AddSingleton<IMigrationOperationSchemaSetter, SqlServerMigrationOperationSchemaSetter>();

if (AddWindowFunctionsSupport)
{
var lifetime = GetLifetime<IEvaluatableExpressionFilterPlugin>();
services.Add(ServiceDescriptor.Describe(typeof(IEvaluatableExpressionFilterPlugin), typeof(WindowFunctionEvaluatableExpressionFilterPlugin), lifetime));

}
}

/// <summary>
Expand Down
Loading

0 comments on commit 29ebadf

Please sign in to comment.