Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EF.Functions.Random #23145

Merged
1 commit merged into from
Dec 22, 2020
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
2 changes: 1 addition & 1 deletion EFCore.Runtime.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"src\\EFCore\\EFCore.csproj",
"src\\Microsoft.Data.Sqlite.Core\\Microsoft.Data.Sqlite.Core.csproj",
"test\\EFCore.Analyzers.Tests\\EFCore.Analyzers.Tests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.AspNet.Specification.Tests\\EFCore.AspNet.Specification.Tests.csproj",
"test\\EFCore.AspNet.SqlServer.FunctionalTests\\EFCore.AspNet.SqlServer.FunctionalTests.csproj",
"test\\EFCore.AspNet.Sqlite.FunctionalTests\\EFCore.AspNet.Sqlite.FunctionalTests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.Cosmos.FunctionalTests\\EFCore.Cosmos.FunctionalTests.csproj",
"test\\EFCore.Cosmos.Tests\\EFCore.Cosmos.Tests.csproj",
"test\\EFCore.CrossStore.FunctionalTests\\EFCore.CrossStore.FunctionalTests.csproj",
Expand Down
3 changes: 3 additions & 0 deletions EFCore.Sqlite.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"src\\EFCore\\EFCore.csproj",
"src\\Microsoft.Data.Sqlite.Core\\Microsoft.Data.Sqlite.Core.csproj",
"test\\EFCore.Analyzers.Tests\\EFCore.Analyzers.Tests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.AspNet.Specification.Tests\\EFCore.AspNet.Specification.Tests.csproj",
"test\\EFCore.AspNet.Sqlite.FunctionalTests\\EFCore.AspNet.Sqlite.FunctionalTests.csproj",
"test\\EFCore.Design.Tests\\EFCore.Design.Tests.csproj",
"test\\EFCore.InMemory.FunctionalTests\\EFCore.InMemory.FunctionalTests.csproj",
"test\\EFCore.InMemory.Tests\\EFCore.InMemory.Tests.csproj",
Expand Down
2 changes: 1 addition & 1 deletion EFCore.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"src\\dotnet-ef\\dotnet-ef.csproj",
"src\\ef\\ef.csproj",
"test\\EFCore.Analyzers.Tests\\EFCore.Analyzers.Tests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.AspNet.Specification.Tests\\EFCore.AspNet.Specification.Tests.csproj",
"test\\EFCore.AspNet.SqlServer.FunctionalTests\\EFCore.AspNet.SqlServer.FunctionalTests.csproj",
"test\\EFCore.AspNet.Sqlite.FunctionalTests\\EFCore.AspNet.Sqlite.FunctionalTests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj"
"test\\EFCore.Cosmos.FunctionalTests\\EFCore.Cosmos.FunctionalTests.csproj",
"test\\EFCore.Cosmos.Tests\\EFCore.Cosmos.Tests.csproj",
"test\\EFCore.CrossStore.FunctionalTests\\EFCore.CrossStore.FunctionalTests.csproj",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public CosmosMethodCallTranslatorProvider(
{
new EqualsTranslator(sqlExpressionFactory),
new StringMethodTranslator(sqlExpressionFactory),
new ContainsTranslator(sqlExpressionFactory)
new ContainsTranslator(sqlExpressionFactory),
new RandomTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
//new GetValueOrDefaultTranslator(sqlExpressionFactory),
Expand Down
59 changes: 59 additions & 0 deletions src/EFCore.Cosmos/Query/Internal/RandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
{
/// <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 class RandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <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 RandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <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)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"RAND",
Array.Empty<SqlExpression>(),
method.ReturnType)
: null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public class InMemoryExpressionTranslatingExpressionVisitor : ExpressionVisitor
private static readonly MethodInfo _likeMethodInfoWithEscape = typeof(DbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(string) });

private static readonly MethodInfo _randomMethodInfo = typeof(DbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });

private static readonly MethodInfo _randomNextDoubleMethodInfo = typeof(Random).GetRequiredRuntimeMethod(
nameof(Random.NextDouble), Array.Empty<Type>());

private static readonly MethodInfo _inMemoryLikeMethodInfo =
typeof(InMemoryExpressionTranslatingExpressionVisitor).GetRequiredDeclaredMethod(nameof(InMemoryLike));

Expand Down Expand Up @@ -750,6 +756,11 @@ static Expression RemapLambda(GroupingElementExpression groupingElement, LambdaE
return Expression.Call(_inMemoryLikeMethodInfo, visitedArguments);
}

if (methodCallExpression.Method == _randomMethodInfo)
{
return Expression.Call(Expression.New(typeof(Random)), _randomNextDoubleMethodInfo);
}

Expression? @object = null;
Expression[] arguments;
var method = methodCallExpression.Method;
Expand Down
64 changes: 64 additions & 0 deletions src/EFCore.Relational/Query/Internal/RandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

#nullable enable

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
/// <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 class RandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRequiredRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <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 RandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <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)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"RAND",
Array.Empty<SqlExpression>(),
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType)
: null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public RelationalMethodCallTranslatorProvider([NotNull] RelationalMethodCallTran
new EnumHasFlagTranslator(sqlExpressionFactory),
new GetValueOrDefaultTranslator(sqlExpressionFactory),
new ComparisonTranslator(sqlExpressionFactory),
new ByteArraySequenceEqualTranslator(sqlExpressionFactory)
new ByteArraySequenceEqualTranslator(sqlExpressionFactory),
new RandomTranslator(sqlExpressionFactory)
});
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat
new SqliteHexMethodTranslator(sqlExpressionFactory),
new SqliteMathTranslator(sqlExpressionFactory),
new SqliteObjectToStringTranslator(sqlExpressionFactory),
new SqliteRandomTranslator(sqlExpressionFactory),
new SqliteRegexMethodTranslator(sqlExpressionFactory),
new SqliteStringMethodTranslator(sqlExpressionFactory),
new SqliteSubstrMethodTranslator(sqlExpressionFactory)
Expand Down
74 changes: 74 additions & 0 deletions src/EFCore.Sqlite.Core/Query/Internal/SqliteRandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal
{
/// <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 class SqliteRandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <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 SqliteRandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <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)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

// Issue #15586: Query: TypeCompatibility chart for inference.
return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"abs",
new SqlExpression[]
{
_sqlExpressionFactory.Divide(
_sqlExpressionFactory.Function(
"random",
Array.Empty<SqlExpression>(),
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType),
RaymondHuy marked this conversation as resolved.
Show resolved Hide resolved
_sqlExpressionFactory.Constant(9223372036854780000.0))
},
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType)
: null;
}
}
}
16 changes: 12 additions & 4 deletions src/EFCore/DbFunctionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static bool Like(
[CanBeNull] this DbFunctions _,
[CanBeNull] string matchExpression,
[CanBeNull] string pattern)
=> LikeCore(matchExpression, pattern, escapeCharacter: null);
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like)));

/// <summary>
/// <para>
Expand All @@ -61,9 +61,17 @@ public static bool Like(
[CanBeNull] string matchExpression,
[CanBeNull] string pattern,
[CanBeNull] string escapeCharacter)
=> LikeCore(matchExpression, pattern, escapeCharacter);

private static bool LikeCore(string matchExpression, string pattern, string escapeCharacter)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like)));

/// <summary>
/// <para>
/// A random double number generator which generates a number between 0 and 1, exclusive.
/// This is usually directly translated to server.
/// </para>
/// </summary>
/// <param name="_"> The DbFunctions instance. </param>
/// <returns> A random double number between 0 and 1, exclusive. </returns>
public static double Random([CanBeNull] this DbFunctions _)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Random)));
RaymondHuy marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.EntityFrameworkCore.Query
{
public class NorthwindDbFunctionsQueryCosmosTest : NorthwindDbFunctionsQueryTestBase<NorthwindQueryCosmosFixture<NoopModelCustomizer>>
{
public NorthwindDbFunctionsQueryCosmosTest(
NorthwindQueryCosmosFixture<NoopModelCustomizer> fixture,
ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
}

public override Task Like_all_literals(bool async)
{
return AssertTranslationFailed(() => base.Like_all_literals(async));
}

public override Task Like_all_literals_with_escape(bool async)
{
return AssertTranslationFailed(() => base.Like_all_literals_with_escape(async));
}

public override Task Like_literal(bool async)
{
return AssertTranslationFailed(() => base.Like_literal(async));
}

public override Task Like_literal_with_escape(bool async)
{
return AssertTranslationFailed(() => base.Like_literal_with_escape(async));
}

public override Task Like_identity(bool async)
{
return AssertTranslationFailed(() => base.Like_identity(async));
}

public override async Task Random_return_less_than_1(bool async)
{
await base.Random_return_less_than_1(async);

AssertSql(
@"SELECT COUNT(1) AS c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (RAND() < 1.0))");
}

public override async Task Random_return_greater_than_0(bool async)
{
await base.Random_return_greater_than_0(async);

AssertSql(
@"SELECT COUNT(1) AS c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (RAND() >= 0.0))");
}

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

protected void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
}
}
Loading