Skip to content

Commit

Permalink
Cosmos: translate string Contains, StartsWith, and EndsWith (#19563)
Browse files Browse the repository at this point in the history
Summary of changes
- Adds initial StringMethodTranslator for Cosmos provider.
- Translates Contains, StartsWith, and EndsWith

Addresses part of #16143
  • Loading branch information
Jacob Viau authored and AndriySvyryd committed Jan 11, 2020
1 parent 3ae7e28 commit 83dccdc
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 26 deletions.
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(
_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

0 comments on commit 83dccdc

Please sign in to comment.