Skip to content

Commit fc89e17

Browse files
committed
Merge branch 'hotfix/5.0.1' into stable
2 parents 96c1797 + 5f1bd38 commit fc89e17

File tree

14 files changed

+254
-114
lines changed

14 files changed

+254
-114
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
dpkg-query -W --showformat='${Package}\n' 'postgresql-*' | xargs sudo dpkg -P postgresql
4545
4646
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
47-
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
47+
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
4848
sudo apt-get update -qq
4949
sudo apt-get install -qq postgresql-${{ matrix.pg_major }} postgresql-${{ matrix.pg_major }}-postgis-${{ env.postgis_version }}
5050
sudo sed -i 's/max_connections = 100/max_connections = 200/g' /etc/postgresql/${{ matrix.pg_major }}/main/postgresql.conf

Directory.Build.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Copyright>Copyright 2020 © The Npgsql Development Team</Copyright>
55
<Company>Npgsql</Company>
6-
<VersionPrefix>5.0.0</VersionPrefix>
6+
<VersionPrefix>5.0.1</VersionPrefix>
77
<TargetFrameworks>netstandard2.1</TargetFrameworks>
88
<PublishRepositoryUrl>true</PublishRepositoryUrl>
99
<PackageLicenseExpression>PostgreSQL</PackageLicenseExpression>
@@ -17,7 +17,7 @@
1717

1818
<!-- Language configuration -->
1919
<PropertyGroup>
20-
<LangVersion>8.0</LangVersion>
20+
<LangVersion>9.0</LangVersion>
2121
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
2222
<AnalysisLevel>latest</AnalysisLevel>
2323
<NoWarn>NU5105</NoWarn>

Directory.Build.targets

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project>
22
<PropertyGroup>
3-
<EFCoreVersion>5.0.0</EFCoreVersion>
3+
<EFCoreVersion>5.0.1</EFCoreVersion>
44
<MicrosoftExtensionsVersion>5.0.0</MicrosoftExtensionsVersion>
5-
<NpgsqlVersion>5.0.0</NpgsqlVersion>
5+
<NpgsqlVersion>5.0.1</NpgsqlVersion>
66
</PropertyGroup>
77

88
<ItemGroup>

src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ protected override bool IsHandledByConvention(IProperty property, IAnnotation an
5555
// and IdentityByDefault otherwise.
5656
if (annotation.Name == NpgsqlAnnotationNames.ValueGenerationStrategy)
5757
{
58-
Debug.Assert(property.ValueGenerated == ValueGenerated.OnAdd);
59-
6058
// Note: both serial and identity-by-default columns are considered by-convention - we don't want
6159
// to assume that the PostgreSQL version of the scaffolded database necessarily determines the
6260
// version of the database that the scaffolded model will target. This makes life difficult for
@@ -201,8 +199,17 @@ IReadOnlyList<MethodCallCodeFragment> GenerateValueGenerationStrategy(
201199
})
202200
};
203201

202+
case NpgsqlValueGenerationStrategy.None:
203+
return new List<MethodCallCodeFragment>
204+
{
205+
new(
206+
nameof(ModelBuilder.HasAnnotation),
207+
NpgsqlAnnotationNames.ValueGenerationStrategy,
208+
NpgsqlValueGenerationStrategy.None)
209+
};
210+
204211
default:
205-
throw new ArgumentOutOfRangeException();
212+
throw new ArgumentOutOfRangeException(strategy.ToString());
206213
}
207214

208215
T GetAndRemove<T>(string annotationName)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using JetBrains.Annotations;
5+
6+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Internal
7+
{
8+
static class IReadOnlyListExtensions
9+
{
10+
public static IReadOnlyList<T> Slice<T>([NotNull] this IReadOnlyList<T> list, int start)
11+
=> new IReadOnlyListSlice<T>(list, start);
12+
13+
sealed class IReadOnlyListSlice<T> : IReadOnlyList<T>
14+
{
15+
IReadOnlyList<T> _underlying;
16+
int _start;
17+
18+
internal IReadOnlyListSlice(IReadOnlyList<T> underlying, int start)
19+
{
20+
_underlying = underlying;
21+
_start = start;
22+
}
23+
24+
public IEnumerator<T> GetEnumerator() => _underlying.Skip(_start).GetEnumerator();
25+
26+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
27+
28+
public int Count => _underlying.Count - _start;
29+
30+
public T this[int index] => _underlying[_start + index];
31+
}
32+
}
33+
}

src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayTranslator.cs

+98-87
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using System.Reflection;
56
using JetBrains.Annotations;
@@ -8,6 +9,7 @@
89
using Microsoft.EntityFrameworkCore.Query;
910
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
1011
using Microsoft.EntityFrameworkCore.Storage;
12+
using Npgsql.EntityFrameworkCore.PostgreSQL.Internal;
1113
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
1214
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
1315
using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics;
@@ -26,7 +28,7 @@ public class NpgsqlArrayTranslator : IMethodCallTranslator, IMemberTranslator
2628
typeof(Enumerable).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
2729
.Single(m => m.Name == nameof(Enumerable.SequenceEqual) && m.GetParameters().Length == 2);
2830

29-
static readonly MethodInfo Contains =
31+
static readonly MethodInfo EnumerableContains =
3032
typeof(Enumerable).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
3133
.Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
3234

@@ -54,108 +56,117 @@ public virtual SqlExpression Translate(
5456
IReadOnlyList<SqlExpression> arguments,
5557
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
5658
{
57-
if (instance != null && instance.Type.IsGenericList() && method.Name == "get_Item" && arguments.Count == 1)
59+
if (instance?.Type.IsGenericList() == true && !IsMappedToNonArray(instance))
5860
{
59-
return
60-
// Try translating indexing inside json column
61-
_jsonPocoTranslator.TranslateMemberAccess(instance, arguments[0], method.ReturnType) ??
62-
// Other types should be subscriptable - but PostgreSQL arrays are 1-based, so adjust the index.
63-
_sqlExpressionFactory.ArrayIndex(instance, GenerateOneBasedIndexExpression(arguments[0]));
64-
}
65-
66-
if (arguments.Count == 0)
67-
return null;
61+
// Translate list[i]. Note that array[i] is translated by NpgsqlSqlTranslatingExpressionVisitor.VisitBinary (ArrayIndex)
62+
if (method.Name == "get_Item" && arguments.Count == 1)
63+
{
64+
return
65+
// Try translating indexing inside json column
66+
_jsonPocoTranslator.TranslateMemberAccess(instance, arguments[0], method.ReturnType) ??
67+
// Other types should be subscriptable - but PostgreSQL arrays are 1-based, so adjust the index.
68+
_sqlExpressionFactory.ArrayIndex(instance, GenerateOneBasedIndexExpression(arguments[0]));
69+
}
6870

69-
var array = arguments[0];
70-
if (!array.Type.TryGetElementType(out var elementType))
71-
return null; // Not an array/list
71+
return TranslateCommon(instance, arguments);
72+
}
7273

73-
// The array/list CLR type may be mapped to a non-array database type (e.g. byte[] to bytea, or just
74-
// value converters). Make sure we're dealing with an array
75-
// Regardless of CLR type, we may be dealing with a non-array database type (e.g. via value converters).
76-
if (array.TypeMapping is RelationalTypeMapping typeMapping &&
77-
!(typeMapping is NpgsqlArrayTypeMapping) && !(typeMapping is NpgsqlJsonTypeMapping))
74+
if (instance is null && arguments.Count > 0 && arguments[0].Type.IsArrayOrGenericList() && !IsMappedToNonArray(arguments[0]))
7875
{
79-
return null;
76+
// Extension method over an array or list
77+
if (method.IsClosedFormOf(SequenceEqual) && arguments[1].Type.IsArray)
78+
return _sqlExpressionFactory.Equal(arguments[0], arguments[1]);
79+
80+
return TranslateCommon(arguments[0], arguments.Slice(1));
8081
}
8182

82-
if (method.IsClosedFormOf(SequenceEqual) && arguments[1].Type.IsArray)
83-
return _sqlExpressionFactory.Equal(array, arguments[1]);
83+
// Not an array/list
84+
return null;
8485

85-
// Predicate-less Any - translate to a simple length check.
86-
if (method.IsClosedFormOf(EnumerableAnyWithoutPredicate))
87-
{
88-
return _sqlExpressionFactory.GreaterThan(
89-
_jsonPocoTranslator.TranslateArrayLength(array) ??
90-
_sqlExpressionFactory.Function(
91-
"cardinality",
92-
arguments,
93-
nullable: true,
94-
argumentsPropagateNullability: TrueArrays[1],
95-
typeof(int)),
96-
_sqlExpressionFactory.Constant(0));
97-
}
86+
// The array/list CLR type may be mapped to a non-array database type (e.g. byte[] to bytea, or just
87+
// value converters) - we don't want to translate for those cases.
88+
static bool IsMappedToNonArray(SqlExpression arrayOrList)
89+
=> arrayOrList.TypeMapping is RelationalTypeMapping typeMapping &&
90+
typeMapping is not (NpgsqlArrayTypeMapping or NpgsqlJsonTypeMapping);
9891

99-
// Note that .Where(e => new[] { "a", "b", "c" }.Any(p => e.SomeText == p)))
100-
// is pattern-matched in AllAnyToContainsRewritingExpressionVisitor, which transforms it to
101-
// new[] { "a", "b", "c" }.Contains(e.Some Text).
102-
103-
if (method.IsClosedFormOf(Contains) &&
104-
(
105-
// Handle either parameters (no mapping but supported CLR type), or array columns. We specifically
106-
// don't want to translate if the type mapping is bytea (CLR type is array, but not an array in
107-
// the database).
108-
array.TypeMapping == null && _typeMappingSource.FindMapping(array.Type) != null ||
109-
array.TypeMapping is NpgsqlArrayTypeMapping
110-
) &&
111-
// Exclude arrays/lists over Nullable<T> since the ADO layer doesn't handle them (but will in 5.0)
112-
Nullable.GetUnderlyingType(elementType) == null)
92+
SqlExpression TranslateCommon(SqlExpression arrayOrList, IReadOnlyList<SqlExpression> arguments)
11393
{
114-
var item = arguments[1];
94+
// Predicate-less Any - translate to a simple length check.
95+
if (method.IsClosedFormOf(EnumerableAnyWithoutPredicate))
96+
{
97+
return _sqlExpressionFactory.GreaterThan(
98+
_jsonPocoTranslator.TranslateArrayLength(arrayOrList) ??
99+
_sqlExpressionFactory.Function(
100+
"cardinality",
101+
new[] { arrayOrList },
102+
nullable: true,
103+
argumentsPropagateNullability: TrueArrays[1],
104+
typeof(int)),
105+
_sqlExpressionFactory.Constant(0));
106+
}
115107

116-
switch (array)
108+
// Note that .Where(e => new[] { "a", "b", "c" }.Any(p => e.SomeText == p)))
109+
// is pattern-matched in AllAnyToContainsRewritingExpressionVisitor, which transforms it to
110+
// new[] { "a", "b", "c" }.Contains(e.Some Text).
111+
112+
if ((method.IsClosedFormOf(EnumerableContains) || // Enumerable.Contains extension method
113+
method.Name == nameof(List<int>.Contains) && method.DeclaringType.IsGenericList() &&
114+
method.GetParameters().Length == 1)
115+
&&
116+
(
117+
// Handle either parameters (no mapping but supported CLR type), or array columns. We specifically
118+
// don't want to translate if the type mapping is bytea (CLR type is array, but not an array in
119+
// the database).
120+
arrayOrList.TypeMapping == null && _typeMappingSource.FindMapping(arrayOrList.Type) != null ||
121+
arrayOrList.TypeMapping is NpgsqlArrayTypeMapping
122+
))
117123
{
118-
// When the array is a column, we translate to array @> ARRAY[item]. GIN indexes
119-
// on array are used, but null semantics is impossible without preventing index use.
120-
case ColumnExpression _:
121-
if (item is SqlConstantExpression constant && constant.Value is null)
124+
var item = arguments[0];
125+
126+
switch (arrayOrList)
122127
{
123-
// We special-case null constant item and use array_position instead, since it does
124-
// nulls correctly (but doesn't use indexes)
125-
// TODO: once lambda-based caching is implemented, move this to NpgsqlSqlNullabilityProcessor
126-
// (https://github.com/dotnet/efcore/issues/17598) and do for parameters as well.
127-
return _sqlExpressionFactory.IsNotNull(
128-
_sqlExpressionFactory.Function(
129-
"array_position",
130-
new[] { array, item },
131-
nullable: true,
132-
argumentsPropagateNullability: FalseArrays[2],
133-
typeof(int)));
128+
// When the array is a column, we translate to array @> ARRAY[item]. GIN indexes
129+
// on array are used, but null semantics is impossible without preventing index use.
130+
case ColumnExpression _:
131+
if (item is SqlConstantExpression constant && constant.Value is null)
132+
{
133+
// We special-case null constant item and use array_position instead, since it does
134+
// nulls correctly (but doesn't use indexes)
135+
// TODO: once lambda-based caching is implemented, move this to NpgsqlSqlNullabilityProcessor
136+
// (https://github.com/dotnet/efcore/issues/17598) and do for parameters as well.
137+
return _sqlExpressionFactory.IsNotNull(
138+
_sqlExpressionFactory.Function(
139+
"array_position",
140+
new[] { arrayOrList, item },
141+
nullable: true,
142+
argumentsPropagateNullability: FalseArrays[2],
143+
typeof(int)));
144+
}
145+
146+
return _sqlExpressionFactory.Contains(arrayOrList,
147+
_sqlExpressionFactory.NewArrayOrConstant(new[] { item }, arrayOrList.Type));
148+
149+
// Don't do anything PG-specific for constant arrays since the general EF Core mechanism is fine
150+
// for that case: item IN (1, 2, 3).
151+
// After https://github.com/aspnet/EntityFrameworkCore/issues/16375 is done we may not need the
152+
// check any more.
153+
case SqlConstantExpression _:
154+
return null;
155+
156+
// For ParameterExpression, and for all other cases - e.g. array returned from some function -
157+
// translate to e.SomeText = ANY (@p). This is superior to the general solution which will expand
158+
// parameters to constants, since non-PG SQL does not support arrays.
159+
// Note that this will allow indexes on the item to be used.
160+
default:
161+
return _sqlExpressionFactory.Any(item, arrayOrList, PostgresAnyOperatorType.Equal);
134162
}
135-
136-
return _sqlExpressionFactory.Contains(array,
137-
_sqlExpressionFactory.NewArrayOrConstant(new[] { item }, array.Type));
138-
139-
// Don't do anything PG-specific for constant arrays since the general EF Core mechanism is fine
140-
// for that case: item IN (1, 2, 3).
141-
// After https://github.com/aspnet/EntityFrameworkCore/issues/16375 is done we may not need the
142-
// check any more.
143-
case SqlConstantExpression _:
144-
return null;
145-
146-
// For ParameterExpression, and for all other cases - e.g. array returned from some function -
147-
// translate to e.SomeText = ANY (@p). This is superior to the general solution which will expand
148-
// parameters to constants, since non-PG SQL does not support arrays.
149-
// Note that this will allow indexes on the item to be used.
150-
default:
151-
return _sqlExpressionFactory.Any(item, array, PostgresAnyOperatorType.Equal);
152163
}
153-
}
154164

155-
// Note: we also translate .Where(e => new[] { "a", "b", "c" }.Any(p => EF.Functions.Like(e.SomeText, p)))
156-
// to LIKE ANY (...). See NpgsqlSqlTranslatingExpressionVisitor.VisitArrayMethodCall.
165+
// Note: we also translate .Where(e => new[] { "a", "b", "c" }.Any(p => EF.Functions.Like(e.SomeText, p)))
166+
// to LIKE ANY (...). See NpgsqlSqlTranslatingExpressionVisitor.VisitArrayMethodCall.
157167

158-
return null;
168+
return null;
169+
}
159170
}
160171

161172
public virtual SqlExpression Translate(SqlExpression instance,

src/EFCore.PG/Query/Expressions/Internal/PostgresAllExpression.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public PostgresAllExpression(
5050
[CanBeNull] RelationalTypeMapping typeMapping)
5151
: base(typeof(bool), typeMapping)
5252
{
53-
if (!array.Type.IsArrayOrGenericList())
53+
if (!(array.Type.IsArrayOrGenericList() || array is SqlConstantExpression { Value: null }))
5454
throw new ArgumentException("Array expression must be of type array or List<>", nameof(array));
5555

5656
Item = item;

src/EFCore.PG/Query/Expressions/Internal/PostgresAnyExpression.cs

+7-4
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@ public PostgresAnyExpression(
5353
[CanBeNull] RelationalTypeMapping typeMapping)
5454
: base(typeof(bool), typeMapping)
5555
{
56-
if (!array.Type.IsArrayOrGenericList())
57-
throw new ArgumentException("Array expression must be of type array or List<>", nameof(array));
58-
if (array is SqlConstantExpression && operatorType == PostgresAnyOperatorType.Equal)
59-
throw new ArgumentException($"Use {nameof(InExpression)} for equality against constant arrays", nameof(array));
56+
if (!(array is SqlConstantExpression { Value: null }))
57+
{
58+
if (!array.Type.IsArrayOrGenericList())
59+
throw new ArgumentException("Array expression must be of type array or List<>", nameof(array));
60+
if (array is SqlConstantExpression && operatorType == PostgresAnyOperatorType.Equal)
61+
throw new ArgumentException($"Use {nameof(InExpression)} for equality against constant arrays", nameof(array));
62+
}
6063

6164
Item = item;
6265
Array = array;

0 commit comments

Comments
 (0)