Skip to content

Commit 4bc916f

Browse files
authored
Translate Guid.CreateVersion7 (#3571)
Closes #3567
1 parent 4052e06 commit 4bc916f

File tree

5 files changed

+96
-51
lines changed

5 files changed

+96
-51
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics;
2+
3+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;
4+
5+
/// <summary>
6+
/// Provides translation services for PostgreSQL UUID functions.
7+
/// </summary>
8+
/// <remarks>
9+
/// See: https://www.postgresql.org/docs/current/datatype-uuid.html
10+
/// </remarks>
11+
public class NpgsqlGuidTranslator(ISqlExpressionFactory sqlExpressionFactory, Version? postgresVersion) : IMethodCallTranslator
12+
{
13+
private readonly string _uuidGenerationFunction = postgresVersion.AtLeast(13) ? "gen_random_uuid" : "uuid_generate_v4";
14+
15+
/// <summary>
16+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
17+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
18+
/// any release. You should only use it directly in your code with extreme caution and knowing that
19+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
20+
/// </summary>
21+
public virtual SqlExpression? Translate(
22+
SqlExpression? instance,
23+
MethodInfo method,
24+
IReadOnlyList<SqlExpression> arguments,
25+
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
26+
{
27+
if (method.DeclaringType == typeof(Guid))
28+
{
29+
return method.Name switch
30+
{
31+
nameof(Guid.NewGuid)
32+
=> sqlExpressionFactory.Function(
33+
_uuidGenerationFunction,
34+
[],
35+
nullable: false,
36+
argumentsPropagateNullability: FalseArrays[0],
37+
method.ReturnType),
38+
39+
// Note: uuidv7() was introduce in PostgreSQL 18.
40+
// In NpgsqlEvaluatableExpressionFilter we only prevent local evaluation when targeting PG18 or later;
41+
// that means that for lower version, the call gets evaluated locally and the result sent as a parameter
42+
// (and we never see the method call here).
43+
nameof(Guid.CreateVersion7)
44+
=> sqlExpressionFactory.Function(
45+
"uuidv7",
46+
[],
47+
nullable: false,
48+
argumentsPropagateNullability: FalseArrays[0],
49+
method.ReturnType),
50+
51+
_ => null
52+
};
53+
}
54+
55+
return null;
56+
}
57+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public NpgsqlMethodCallTranslatorProvider(
5555
LTreeTranslator,
5656
new NpgsqlMathTranslator(typeMappingSource, sqlExpressionFactory, model),
5757
new NpgsqlNetworkTranslator(typeMappingSource, sqlExpressionFactory, model),
58-
new NpgsqlNewGuidTranslator(sqlExpressionFactory, npgsqlOptions.PostgresVersion),
58+
new NpgsqlGuidTranslator(sqlExpressionFactory, npgsqlOptions.PostgresVersion),
5959
new NpgsqlObjectToStringTranslator(typeMappingSource, sqlExpressionFactory),
6060
new NpgsqlRandomTranslator(sqlExpressionFactory),
6161
new NpgsqlRangeTranslator(typeMappingSource, sqlExpressionFactory, model, supportsMultiranges),

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

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/EFCore.PG/Query/Internal/NpgsqlEvaluatableExpressionFilter.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Runtime.CompilerServices;
2+
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
23

34
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;
45

@@ -10,6 +11,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;
1011
/// </summary>
1112
public class NpgsqlEvaluatableExpressionFilter : RelationalEvaluatableExpressionFilter
1213
{
14+
private readonly Version _postgresVersion;
15+
1316
private static readonly MethodInfo TsQueryParse =
1417
typeof(NpgsqlTsQuery).GetRuntimeMethod(nameof(NpgsqlTsQuery.Parse), [typeof(string)])!;
1518

@@ -24,9 +27,11 @@ public class NpgsqlEvaluatableExpressionFilter : RelationalEvaluatableExpression
2427
/// </summary>
2528
public NpgsqlEvaluatableExpressionFilter(
2629
EvaluatableExpressionFilterDependencies dependencies,
27-
RelationalEvaluatableExpressionFilterDependencies relationalDependencies)
30+
RelationalEvaluatableExpressionFilterDependencies relationalDependencies,
31+
INpgsqlSingletonOptions npgsqlSingletonOptions)
2832
: base(dependencies, relationalDependencies)
2933
{
34+
_postgresVersion = npgsqlSingletonOptions.PostgresVersion;
3035
}
3136

3237
/// <summary>
@@ -51,6 +56,8 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
5156
|| declaringType == typeof(NpgsqlNetworkDbFunctionsExtensions)
5257
|| declaringType == typeof(NpgsqlJsonDbFunctionsExtensions)
5358
|| declaringType == typeof(NpgsqlRangeDbFunctionsExtensions)
59+
// PG18 introduced uuidv7(), so we prevent local evaluation when targeting PG18 or later.
60+
|| declaringType == typeof(Guid) && method.Name == nameof(Guid.CreateVersion7) && _postgresVersion.AtLeast(18)
5461
// Prevent evaluation of ValueTuple.Create, see NewExpression of ITuple below
5562
|| declaringType == typeof(ValueTuple) && method.Name == nameof(ValueTuple.Create))
5663
{

test/EFCore.PG.FunctionalTests/Query/Translations/GuidTranslationsNpgsqlTest.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel;
2+
13
namespace Microsoft.EntityFrameworkCore.Query.Translations;
24

35
public class GuidTranslationsNpgsqlTest : GuidTranslationsTestBase<BasicTypesQueryNpgsqlFixture>
@@ -71,6 +73,34 @@ WHERE uuid_generate_v4() <> '00000000-0000-0000-0000-000000000000'
7173
}
7274
}
7375

76+
[ConditionalTheory]
77+
[MemberData(nameof(IsAsyncData))]
78+
public virtual async Task CreateVersion7(bool async)
79+
{
80+
await AssertQuery(
81+
async,
82+
ss => ss.Set<BasicTypesEntity>()
83+
.Where(od => Guid.CreateVersion7() != default));
84+
85+
if (TestEnvironment.PostgresVersion >= new Version(18, 0))
86+
{
87+
AssertSql(
88+
"""
89+
SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan"
90+
FROM "BasicTypesEntities" AS b
91+
WHERE uuidv7() <> '00000000-0000-0000-0000-000000000000'
92+
""");
93+
}
94+
else
95+
{
96+
AssertSql(
97+
"""
98+
SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan"
99+
FROM "BasicTypesEntities" AS b
100+
""");
101+
}
102+
}
103+
74104
private void AssertSql(params string[] expected)
75105
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
76106
}

0 commit comments

Comments
 (0)