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

Cosmos: translate string Contains, StartsWith, and EndsWith #19563

Merged
merged 4 commits into from
Jan 11, 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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public CosmosMethodCallTranslatorProvider(
new IMethodCallTranslator[]
{
new EqualsTranslator(sqlExpressionFactory),
//new StringMethodTranslator(sqlExpressionFactory),
new StringMethodTranslator(sqlExpressionFactory),
new ContainsTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
Expand Down
3 changes: 3 additions & 0 deletions src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public virtual SqlExpression ApplyTypeMapping(SqlExpression sqlExpression, CoreT
case SqlParameterExpression sqlParameterExpression:
return sqlParameterExpression.ApplyTypeMapping(typeMapping);

case SqlFunctionExpression sqlFunctionExpression:
return sqlFunctionExpression.ApplyTypeMapping(typeMapping);

default:
return sqlExpression;
}
Expand Down
81 changes: 81 additions & 0 deletions src/EFCore.Cosmos/Query/Internal/StringMethodTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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.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 StringMethodTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _containsMethodInfo
= typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) });

private static readonly MethodInfo _startsWithMethodInfo
= typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) });

private static readonly MethodInfo _endsWithMethodInfo
= typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) });

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

if (_containsMethodInfo.Equals(method))
{
return TranslateSystemFunction("CONTAINS", instance, arguments[0], typeof(bool));
}

if (_startsWithMethodInfo.Equals(method))
{
return TranslateSystemFunction("STARTSWITH", instance, arguments[0], typeof(bool));
}

if (_endsWithMethodInfo.Equals(method))
{
return TranslateSystemFunction("ENDSWITH", instance, arguments[0], typeof(bool));
}

return null;
}

private SqlExpression TranslateSystemFunction(string function, SqlExpression instance, SqlExpression pattern, Type returnType)
{
Check.NotNull(instance, nameof(instance));
return _sqlExpressionFactory.ApplyDefaultTypeMapping(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ApplyDefaultTypeMapping shouldn't be required. If it does not apply default at later stage automatically then there is bug somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Can a different issue be opened for that? It may be a test validation issue - it is an error along the lines of "SqlExpression without TypeMapping found in query" and the test fails.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #19567

_sqlExpressionFactory.Function(
function,
new[] { instance, pattern },
returnType));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,136 +19,123 @@ public NorthwindFunctionsQueryCosmosTest(
//Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_Literal(bool async)
{
await base.String_StartsWith_Literal(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""M"" != null) AND STARTSWITH(c[""ContactName""], ""M""))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_Identity(bool async)
{
await base.String_StartsWith_Identity(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND STARTSWITH(c[""ContactName""], c[""ContactName""])))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_Column(bool async)
{
await base.String_StartsWith_Column(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND STARTSWITH(c[""ContactName""], c[""ContactName""])))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_MethodCall(bool async)
{
await base.String_StartsWith_MethodCall(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""M"" != null) AND STARTSWITH(c[""ContactName""], ""M""))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_Literal(bool async)
{
await base.String_EndsWith_Literal(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""b"" != null) AND ENDSWITH(c[""ContactName""], ""b""))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_Identity(bool async)
{
await base.String_EndsWith_Identity(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND ENDSWITH(c[""ContactName""], c[""ContactName""])))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_Column(bool async)
{
await base.String_EndsWith_Column(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND ENDSWITH(c[""ContactName""], c[""ContactName""])))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_MethodCall(bool async)
{
await base.String_EndsWith_MethodCall(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""m"" != null) AND ENDSWITH(c[""ContactName""], ""m""))))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_Literal(bool async)
{
await base.String_Contains_Literal(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], ""M""))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_Identity(bool async)
{
await base.String_Contains_Identity(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], c[""ContactName""]))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_Column(bool async)
{
await base.String_Contains_Column(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], c[""ContactName""]))");
}

[ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_MethodCall(bool async)
{
await base.String_Contains_MethodCall(async);

AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], ""M""))");
}

[ConditionalTheory(Skip = "Issue #17246")]
Expand Down