forked from npgsql/efcore.pg
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes npgsql#2349
- Loading branch information
Showing
12 changed files
with
511 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRowValueComparisonTranslator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Runtime.CompilerServices; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; | ||
|
||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal; | ||
|
||
public class NpgsqlRowValueComparisonTranslator : IMethodCallTranslator | ||
{ | ||
private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; | ||
|
||
private static readonly MethodInfo GreaterThan = | ||
typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( | ||
nameof(NpgsqlDbFunctionsExtensions.GreaterThan), | ||
new[] { typeof(DbFunctions), typeof(ITuple), typeof(ITuple) })!; | ||
|
||
private static readonly MethodInfo LessThan = | ||
typeof(NpgsqlDbFunctionsExtensions).GetMethods() | ||
.Single(m => m.Name == nameof(NpgsqlDbFunctionsExtensions.LessThan)); | ||
|
||
private static readonly MethodInfo GreaterThanOrEqual = | ||
typeof(NpgsqlDbFunctionsExtensions).GetMethods() | ||
.Single(m => m.Name == nameof(NpgsqlDbFunctionsExtensions.GreaterThanOrEqual)); | ||
|
||
private static readonly MethodInfo LessThanOrEqual = | ||
typeof(NpgsqlDbFunctionsExtensions).GetMethods() | ||
.Single(m => m.Name == nameof(NpgsqlDbFunctionsExtensions.LessThanOrEqual)); | ||
|
||
private static readonly Dictionary<MethodInfo, ExpressionType> Methods = new() | ||
{ | ||
{ GreaterThan, ExpressionType.GreaterThan }, | ||
{ LessThan, ExpressionType.LessThan }, | ||
{ GreaterThanOrEqual, ExpressionType.GreaterThanOrEqual }, | ||
{ LessThanOrEqual, ExpressionType.LessThanOrEqual } | ||
}; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="NpgsqlRowValueComparisonTranslator"/> class. | ||
/// </summary> | ||
public NpgsqlRowValueComparisonTranslator(NpgsqlSqlExpressionFactory sqlExpressionFactory) | ||
=> _sqlExpressionFactory = sqlExpressionFactory; | ||
|
||
/// <inheritdoc /> | ||
public virtual SqlExpression? Translate( | ||
SqlExpression? instance, | ||
MethodInfo method, | ||
IReadOnlyList<SqlExpression> arguments, | ||
IDiagnosticsLogger<DbLoggerCategory.Query> logger) | ||
{ | ||
if (method.DeclaringType != typeof(NpgsqlDbFunctionsExtensions) | ||
|| !Methods.TryGetValue(method, out var expressionType) | ||
|| arguments[1] is not PostgresRowValueExpression rowValue1 | ||
|| arguments[2] is not PostgresRowValueExpression rowValue2) | ||
{ | ||
return null; | ||
} | ||
|
||
if (rowValue1.Values.Count != rowValue2.Values.Count) | ||
{ | ||
throw new ArgumentException(NpgsqlStrings.RowValueMethodRequiresTwoArraysOfSameLength(method.Name)); | ||
} | ||
|
||
return _sqlExpressionFactory.MakeBinary(expressionType, rowValue1, rowValue2, typeMapping: null); | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
src/EFCore.PG/Query/Expressions/Internal/PostgresRowValueExpression.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Runtime.CompilerServices; | ||
|
||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; | ||
|
||
/// <summary> | ||
/// An expression that represents a PostgreSQL-specific row value expression in a SQL tree. | ||
/// </summary> | ||
/// <remarks> | ||
/// See the <see href="https://www.postgresql.org/docs/current/sql-expressions.html#SQL-SYNTAX-ROW-CONSTRUCTORS">PostgreSQL docs</see> | ||
/// for more information. | ||
/// </remarks> | ||
public class PostgresRowValueExpression : SqlExpression, IEquatable<PostgresRowValueExpression> | ||
{ | ||
/// <summary> | ||
/// The values of this PostgreSQL row value expression. | ||
/// </summary> | ||
public virtual IReadOnlyList<SqlExpression> Values { get; } | ||
|
||
/// <inheritdoc /> | ||
public PostgresRowValueExpression(IReadOnlyList<SqlExpression> values) | ||
: base(typeof(ITuple), typeMapping: RowValueTypeMapping.Instance) | ||
{ | ||
Check.NotNull(values, nameof(values)); | ||
|
||
Values = values; | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override Expression VisitChildren(ExpressionVisitor visitor) | ||
{ | ||
Check.NotNull(visitor, nameof(visitor)); | ||
|
||
SqlExpression[]? newRowValues = null; | ||
|
||
for (var i = 0; i < Values.Count; i++) | ||
{ | ||
var rowValue = Values[i]; | ||
var visited = (SqlExpression)visitor.Visit(rowValue); | ||
if (visited != rowValue && newRowValues is null) | ||
{ | ||
newRowValues = new SqlExpression[Values.Count]; | ||
for (var j = 0; j < i; i++) | ||
{ | ||
newRowValues[j] = Values[j]; | ||
} | ||
} | ||
|
||
if (newRowValues is not null) | ||
{ | ||
newRowValues[i] = visited; | ||
} | ||
} | ||
|
||
return newRowValues is null ? this : new PostgresRowValueExpression(newRowValues); | ||
} | ||
|
||
public virtual PostgresRowValueExpression Update(IReadOnlyList<SqlExpression> values) | ||
=> values.Count == Values.Count && values.Zip(Values, (x, y) => (x, y)).All(tup => tup.x == tup.y) | ||
? this | ||
: new PostgresRowValueExpression(values); | ||
|
||
/// <inheritdoc /> | ||
protected override void Print(ExpressionPrinter expressionPrinter) | ||
{ | ||
expressionPrinter.Append("("); | ||
|
||
var count = Values.Count; | ||
for (var i = 0; i < count; i++) | ||
{ | ||
expressionPrinter.Visit(Values[i]); | ||
|
||
if (i < count - 1) | ||
{ | ||
expressionPrinter.Append(", "); | ||
} | ||
} | ||
|
||
expressionPrinter.Append(")"); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override bool Equals(object? obj) | ||
=> obj is PostgresRowValueExpression other && Equals(other); | ||
|
||
/// <inheritdoc /> | ||
public virtual bool Equals(PostgresRowValueExpression? other) | ||
{ | ||
if (other is null || !base.Equals(other) || other.Values.Count != Values.Count) | ||
{ | ||
return false; | ||
} | ||
|
||
if (ReferenceEquals(this, other)) | ||
{ | ||
return true; | ||
} | ||
|
||
for (var i = 0; i < Values.Count; i++) | ||
{ | ||
if (other.Values[i].Equals(Values[i])) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override int GetHashCode() | ||
{ | ||
var hashCode = new HashCode(); | ||
|
||
foreach (var rowValue in Values) | ||
{ | ||
hashCode.Add(rowValue); | ||
} | ||
|
||
return hashCode.ToHashCode(); | ||
} | ||
|
||
/// <summary> | ||
/// Every node in the SQL tree must have a type mapping, but row values aren't actual values (in the sense that they can be sent as | ||
/// parameters, or have a literal representation). So we have a dummy type mapping for that. | ||
/// </summary> | ||
private sealed class RowValueTypeMapping : RelationalTypeMapping | ||
{ | ||
internal static RowValueTypeMapping Instance { get; } = new(); | ||
|
||
private RowValueTypeMapping() | ||
: base(new(new(), storeType: "rowvalue")) | ||
{ | ||
} | ||
|
||
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) | ||
=> this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.