Skip to content

Commit 83842cf

Browse files
committed
Support queryable array parameters and columns
Closes npgsql#2677
1 parent 4781432 commit 83842cf

29 files changed

+2201
-321
lines changed

Diff for: Directory.Build.props

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<PackageLicenseExpression>PostgreSQL</PackageLicenseExpression>
1919
<PackageProjectUrl>https://github.com/npgsql/efcore.pg</PackageProjectUrl>
2020
<PackageIcon>postgresql.png</PackageIcon>
21+
<NoWarn>$(NoWarn);CS1591</NoWarn>
2122
</PropertyGroup>
2223

2324
<ItemGroup>

Diff for: Directory.Packages.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
3-
<EFCoreVersion>8.0.0-preview.3.23166.2</EFCoreVersion>
4-
<MicrosoftExtensionsVersion>8.0.0-preview.3.23162.2</MicrosoftExtensionsVersion>
3+
<EFCoreVersion>8.0.0-preview.4.23218.4</EFCoreVersion>
4+
<MicrosoftExtensionsVersion>8.0.0-preview.4.23218.4</MicrosoftExtensionsVersion>
55
<NpgsqlVersion>8.0.0-preview.2</NpgsqlVersion>
66
</PropertyGroup>
77

Diff for: src/EFCore.PG.NTS/EFCore.PG.NTS.csproj

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
<PropertyGroup>
33
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite</AssemblyName>
44
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite</RootNamespace>
5-
<TargetFramework>net6.0</TargetFramework>
6-
<TargetFramework Condition="'$(DeveloperBuild)' == 'True'">net8.0</TargetFramework>
5+
<TargetFramework>net7.0</TargetFramework>
76

87
<Authors>Shay Rojansky</Authors>
98
<Description>NetTopologySuite PostGIS spatial support plugin for PostgreSQL/Npgsql Entity Framework Core provider.</Description>

Diff for: src/EFCore.PG.NodaTime/EFCore.PG.NodaTime.csproj

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
<PropertyGroup>
33
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime</AssemblyName>
44
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime</RootNamespace>
5-
<TargetFramework>net6.0</TargetFramework>
6-
<TargetFramework Condition="'$(DeveloperBuild)' == 'True'">net8.0</TargetFramework>
5+
<TargetFramework>net7.0</TargetFramework>
76

87
<Authors>Shay Rojansky</Authors>
98
<Description>NodaTime support plugin for PostgreSQL/Npgsql Entity Framework Core provider.</Description>

Diff for: src/EFCore.PG/EFCore.PG.csproj

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
<PropertyGroup>
44
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL</AssemblyName>
55
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL</RootNamespace>
6-
<TargetFrameworks Condition="'$(DeveloperBuild)' != 'True'">net6.0;net7.0;net8.0</TargetFrameworks>
7-
<TargetFramework Condition="'$(DeveloperBuild)' == 'True'">net8.0</TargetFramework>
6+
<TargetFramework>net7.0</TargetFramework>
87

98
<Authors>Shay Rojansky;Austin Drenski;Yoh Deadfall;</Authors>
109
<Description>PostgreSQL/Npgsql provider for Entity Framework Core.</Description>

Diff for: src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Data.Common;
21
using Npgsql.EntityFrameworkCore.PostgreSQL.Diagnostics.Internal;
32
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
43
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
@@ -114,6 +113,7 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio
114113
.TryAdd<IEvaluatableExpressionFilter, NpgsqlEvaluatableExpressionFilter>()
115114
.TryAdd<IQuerySqlGeneratorFactory, NpgsqlQuerySqlGeneratorFactory>()
116115
.TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, NpgsqlSqlTranslatingExpressionVisitorFactory>()
116+
.TryAdd<IQueryTranslationPreprocessorFactory, NpgsqlQueryTranslationPreprocessorFactory>()
117117
.TryAdd<IQueryTranslationPostprocessorFactory, NpgsqlQueryTranslationPostprocessorFactory>()
118118
.TryAdd<IRelationalParameterBasedSqlProcessorFactory, NpgsqlParameterBasedSqlProcessorFactory>()
119119
.TryAdd<ISqlExpressionFactory, NpgsqlSqlExpressionFactory>()
@@ -130,4 +130,4 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio
130130

131131
return serviceCollection;
132132
}
133-
}
133+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
2+
3+
/// <summary>
4+
/// A SQL expression that represents a slicing into a PostgreSQL array (e.g. array[2:3]).
5+
/// </summary>
6+
/// <remarks>
7+
/// <see href="https://www.postgresql.org/docs/current/arrays.html#ARRAYS-ACCESSING" />.
8+
/// </remarks>
9+
public class PostgresArraySliceExpression : SqlExpression, IEquatable<PostgresArraySliceExpression>
10+
{
11+
/// <summary>
12+
/// The array being sliced.
13+
/// </summary>
14+
public virtual SqlExpression Array { get; }
15+
16+
/// <summary>
17+
/// The lower bound of the slice.
18+
/// </summary>
19+
public virtual SqlExpression? LowerBound { get; }
20+
21+
/// <summary>
22+
/// The upper bound of the slice.
23+
/// </summary>
24+
public virtual SqlExpression? UpperBound { get; }
25+
26+
/// <summary>
27+
/// Creates a new instance of the <see cref="PostgresArraySliceExpression" /> class.
28+
/// </summary>
29+
/// <param name="array">The array tp slice into.</param>
30+
/// <param name="lowerBound">The lower bound of the slice.</param>
31+
/// <param name="upperBound">The upper bound of the slice.</param>
32+
public PostgresArraySliceExpression(
33+
SqlExpression array,
34+
SqlExpression? lowerBound,
35+
SqlExpression? upperBound)
36+
: base(array.Type, array.TypeMapping)
37+
{
38+
Check.NotNull(array, nameof(array));
39+
40+
if (lowerBound is null && upperBound is null)
41+
{
42+
throw new ArgumentException("At least one of lowerBound or upperBound must be provided");
43+
}
44+
45+
Array = array;
46+
LowerBound = lowerBound;
47+
UpperBound = upperBound;
48+
}
49+
50+
/// <summary>
51+
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
52+
/// return this expression.
53+
/// </summary>
54+
/// <param name="array">The <see cref="Array" /> property of the result.</param>
55+
/// <param name="lowerBound">The lower bound of the slice.</param>
56+
/// <param name="upperBound">The upper bound of the slice.</param>
57+
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
58+
public virtual PostgresArraySliceExpression Update(SqlExpression array, SqlExpression? lowerBound, SqlExpression? upperBound)
59+
=> array == Array && lowerBound == LowerBound && upperBound == UpperBound
60+
? this
61+
: new PostgresArraySliceExpression(array, lowerBound, upperBound);
62+
63+
/// <inheritdoc />
64+
protected override Expression VisitChildren(ExpressionVisitor visitor)
65+
=> Update(
66+
(SqlExpression)visitor.Visit(Array),
67+
(SqlExpression?)visitor.Visit(LowerBound),
68+
(SqlExpression?)visitor.Visit(UpperBound));
69+
70+
/// <inheritdoc />
71+
public virtual bool Equals(PostgresArraySliceExpression? other)
72+
=> ReferenceEquals(this, other)
73+
|| other is not null
74+
&& base.Equals(other)
75+
&& Array.Equals(other.Array)
76+
&& (LowerBound is null ? other.LowerBound is null : LowerBound.Equals(other.LowerBound))
77+
&& (UpperBound is null ? other.UpperBound is null : UpperBound.Equals(other.UpperBound));
78+
79+
/// <inheritdoc />
80+
public override bool Equals(object? obj) => obj is PostgresArraySliceExpression e && Equals(e);
81+
82+
/// <inheritdoc />
83+
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Array, LowerBound, UpperBound);
84+
85+
/// <inheritdoc />
86+
protected override void Print(ExpressionPrinter expressionPrinter)
87+
{
88+
expressionPrinter.Visit(Array);
89+
expressionPrinter.Append("[");
90+
expressionPrinter.Visit(LowerBound);
91+
expressionPrinter.Append(":");
92+
expressionPrinter.Visit(UpperBound);
93+
expressionPrinter.Append("]");
94+
}
95+
96+
/// <inheritdoc />
97+
public override string ToString() => $"{Array}[{LowerBound}:{UpperBound}]";
98+
}

Diff for: src/EFCore.PG/Query/Expressions/Internal/PostgresBinaryExpression.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ protected override void Print(ExpressionPrinter expressionPrinter)
123123
PostgresExpressionType.LTreeMatches
124124
when Right.TypeMapping?.StoreType == "lquery" ||
125125
Right.TypeMapping is NpgsqlArrayTypeMapping arrayMapping &&
126-
arrayMapping.ElementMapping.StoreType == "lquery"
126+
arrayMapping.ElementTypeMapping.StoreType == "lquery"
127127
=> "~",
128128
PostgresExpressionType.LTreeMatches
129129
when Right.TypeMapping?.StoreType == "ltxtquery"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
2+
3+
/// <summary>
4+
/// An expression that represents a PostgreSQL <c>unnest</c> function call in a SQL tree.
5+
/// </summary>
6+
/// <remarks>
7+
/// <para>
8+
/// This expression is just a <see cref="TableValuedFunctionExpression" />, adding the ability to provide an explicit column name
9+
/// for its output (<c>SELECT * FROM unnest(array) AS f(foo)</c>). This is necessary since when the column name isn't explicitly
10+
/// specified, it is automatically identical to the table alias (<c>f</c> above); since the table alias may get uniquified by
11+
/// EF, this would break queries.
12+
/// </para>
13+
/// <para>
14+
/// See <see href="https://www.postgresql.org/docs/current/functions-array.html#ARRAY-FUNCTIONS-TABLE">unnest</see> for more
15+
/// information and examples.
16+
/// </para>
17+
/// <para>
18+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
19+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
20+
/// any release. You should only use it directly in your code with extreme caution and knowing that
21+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
22+
/// </para>
23+
/// </remarks>
24+
public class PostgresUnnestExpression : TableValuedFunctionExpression
25+
{
26+
/// <summary>
27+
/// The array to be un-nested into a table.
28+
/// </summary>
29+
/// <remarks>
30+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
31+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
32+
/// any release. You should only use it directly in your code with extreme caution and knowing that
33+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
34+
/// </remarks>
35+
public virtual SqlExpression Array
36+
=> Arguments[0];
37+
38+
/// <summary>
39+
/// The name of the column to be projected out from the <c>unnest</c> call.
40+
/// </summary>
41+
/// <remarks>
42+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
43+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
44+
/// any release. You should only use it directly in your code with extreme caution and knowing that
45+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
46+
/// </remarks>
47+
public virtual string ColumnName { get; }
48+
49+
/// <summary>
50+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
51+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
52+
/// any release. You should only use it directly in your code with extreme caution and knowing that
53+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
54+
/// </summary>
55+
public PostgresUnnestExpression(SqlExpression array, string columnName)
56+
: this("u", array, columnName)
57+
{
58+
}
59+
60+
/// <summary>
61+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
62+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
63+
/// any release. You should only use it directly in your code with extreme caution and knowing that
64+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
65+
/// </summary>
66+
public PostgresUnnestExpression(string alias, SqlExpression array, string columnName)
67+
: base(alias, "unnest", schema: null, builtIn: true, new[] { array })
68+
{
69+
ColumnName = columnName;
70+
}
71+
72+
/// <summary>
73+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
74+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
75+
/// any release. You should only use it directly in your code with extreme caution and knowing that
76+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
77+
/// </summary>
78+
public override TableValuedFunctionExpression Update(IReadOnlyList<SqlExpression> arguments)
79+
=> arguments is [var singleArgument]
80+
? Update(singleArgument)
81+
: throw new ArgumentException();
82+
83+
/// <summary>
84+
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
85+
/// return this expression.
86+
/// </summary>
87+
/// <param name="array">The <see cref="Array" /> property of the result.</param>
88+
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
89+
public virtual PostgresUnnestExpression Update(SqlExpression array)
90+
=> array != Array
91+
? new PostgresUnnestExpression(Alias, array, ColumnName)
92+
: this;
93+
94+
/// <inheritdoc />
95+
protected override void Print(ExpressionPrinter expressionPrinter)
96+
{
97+
expressionPrinter.Append(Name);
98+
expressionPrinter.Append("(");
99+
expressionPrinter.VisitCollection(Arguments);
100+
expressionPrinter.Append(")");
101+
102+
PrintAnnotations(expressionPrinter);
103+
expressionPrinter
104+
.Append(" AS ")
105+
.Append(Alias)
106+
.Append("(")
107+
.Append(ColumnName)
108+
.Append(")");
109+
}
110+
111+
/// <inheritdoc />
112+
public override bool Equals(object? obj)
113+
=> obj != null
114+
&& (ReferenceEquals(this, obj)
115+
|| obj is PostgresUnnestExpression unnestExpression
116+
&& Equals(unnestExpression));
117+
118+
private bool Equals(PostgresUnnestExpression unnestExpression)
119+
=> base.Equals(unnestExpression) && ColumnName == unnestExpression.ColumnName;
120+
121+
/// <inheritdoc />
122+
public override int GetHashCode()
123+
=> base.GetHashCode();
124+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;
2+
3+
/// <summary>
4+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
5+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
6+
/// any release. You should only use it directly in your code with extreme caution and knowing that
7+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
8+
/// </summary>
9+
public class NpgsqlQueryRootProcessor : RelationalQueryRootProcessor
10+
{
11+
/// <summary>
12+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
13+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
14+
/// any release. You should only use it directly in your code with extreme caution and knowing that
15+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
16+
/// </summary>
17+
public NpgsqlQueryRootProcessor(
18+
QueryTranslationPreprocessorDependencies dependencies,
19+
RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
20+
QueryCompilationContext queryCompilationContext)
21+
: base(dependencies, relationalDependencies, queryCompilationContext)
22+
{
23+
}
24+
25+
/// <summary>
26+
/// Converts a <see cref="ParameterExpression" /> to a <see cref="ParameterQueryRootExpression" />, to be later translated to
27+
/// PostgreSQL <c>unnest</c> over an array parameter.
28+
/// </summary>
29+
/// <remarks>
30+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
31+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
32+
/// any release. You should only use it directly in your code with extreme caution and knowing that
33+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
34+
/// </remarks>
35+
protected override bool ShouldConvertToQueryRoot(ParameterExpression parameterExpression)
36+
=> true;
37+
}

0 commit comments

Comments
 (0)