Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;

/// <summary>
/// Provides translation services for PostgreSQL UUID functions.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/datatype-uuid.html
/// </remarks>
public class NpgsqlGuidTranslator(ISqlExpressionFactory sqlExpressionFactory, Version? postgresVersion) : IMethodCallTranslator
{
private readonly string _uuidGenerationFunction = postgresVersion.AtLeast(13) ? "gen_random_uuid" : "uuid_generate_v4";

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression? Translate(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (method.DeclaringType == typeof(Guid))
{
return method.Name switch
{
nameof(Guid.NewGuid)
=> sqlExpressionFactory.Function(
_uuidGenerationFunction,
[],
nullable: false,
argumentsPropagateNullability: FalseArrays[0],
method.ReturnType),

// Note: uuidv7() was introduce in PostgreSQL 18.
// In NpgsqlEvaluatableExpressionFilter we only prevent local evaluation when targeting PG18 or later;
// that means that for lower version, the call gets evaluated locally and the result sent as a parameter
// (and we never see the method call here).
nameof(Guid.CreateVersion7)
=> sqlExpressionFactory.Function(
"uuidv7",
[],
nullable: false,
argumentsPropagateNullability: FalseArrays[0],
method.ReturnType),

_ => null
};
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public NpgsqlMethodCallTranslatorProvider(
LTreeTranslator,
new NpgsqlMathTranslator(typeMappingSource, sqlExpressionFactory, model),
new NpgsqlNetworkTranslator(typeMappingSource, sqlExpressionFactory, model),
new NpgsqlNewGuidTranslator(sqlExpressionFactory, npgsqlOptions.PostgresVersion),
new NpgsqlGuidTranslator(sqlExpressionFactory, npgsqlOptions.PostgresVersion),
new NpgsqlObjectToStringTranslator(typeMappingSource, sqlExpressionFactory),
new NpgsqlRandomTranslator(sqlExpressionFactory),
new NpgsqlRangeTranslator(typeMappingSource, sqlExpressionFactory, model, supportsMultiranges),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;

Expand All @@ -10,6 +11,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;
/// </summary>
public class NpgsqlEvaluatableExpressionFilter : RelationalEvaluatableExpressionFilter
{
private readonly Version _postgresVersion;

private static readonly MethodInfo TsQueryParse =
typeof(NpgsqlTsQuery).GetRuntimeMethod(nameof(NpgsqlTsQuery.Parse), [typeof(string)])!;

Expand All @@ -24,9 +27,11 @@ public class NpgsqlEvaluatableExpressionFilter : RelationalEvaluatableExpression
/// </summary>
public NpgsqlEvaluatableExpressionFilter(
EvaluatableExpressionFilterDependencies dependencies,
RelationalEvaluatableExpressionFilterDependencies relationalDependencies)
RelationalEvaluatableExpressionFilterDependencies relationalDependencies,
INpgsqlSingletonOptions npgsqlSingletonOptions)
: base(dependencies, relationalDependencies)
{
_postgresVersion = npgsqlSingletonOptions.PostgresVersion;
}

/// <summary>
Expand All @@ -51,6 +56,8 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
|| declaringType == typeof(NpgsqlNetworkDbFunctionsExtensions)
|| declaringType == typeof(NpgsqlJsonDbFunctionsExtensions)
|| declaringType == typeof(NpgsqlRangeDbFunctionsExtensions)
// PG18 introduced uuidv7(), so we prevent local evaluation when targeting PG18 or later.
|| declaringType == typeof(Guid) && method.Name == nameof(Guid.CreateVersion7) && _postgresVersion.AtLeast(18)
// Prevent evaluation of ValueTuple.Create, see NewExpression of ITuple below
|| declaringType == typeof(ValueTuple) && method.Name == nameof(ValueTuple.Create))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel;

namespace Microsoft.EntityFrameworkCore.Query.Translations;

public class GuidTranslationsNpgsqlTest : GuidTranslationsTestBase<BasicTypesQueryNpgsqlFixture>
Expand Down Expand Up @@ -71,6 +73,34 @@ WHERE uuid_generate_v4() <> '00000000-0000-0000-0000-000000000000'
}
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task CreateVersion7(bool async)
{
await AssertQuery(
async,
ss => ss.Set<BasicTypesEntity>()
.Where(od => Guid.CreateVersion7() != default));

if (TestEnvironment.PostgresVersion >= new Version(18, 0))
{
AssertSql(
"""
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"
FROM "BasicTypesEntities" AS b
WHERE uuidv7() <> '00000000-0000-0000-0000-000000000000'
""");
}
else
{
AssertSql(
"""
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"
FROM "BasicTypesEntities" AS b
""");
}
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}