Skip to content

Commit

Permalink
Rename Cosmos MethodCallTranslators. Support multiple RegexOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
Marusyk committed May 29, 2022
1 parent b8cba0c commit 4e8da44
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// 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 ContainsTranslator : IMethodCallTranslator
public class CosmosContainsTranslator : IMethodCallTranslator
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;

Expand All @@ -19,7 +19,7 @@ public class ContainsTranslator : IMethodCallTranslator
/// 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 ContainsTranslator(ISqlExpressionFactory sqlExpressionFactory)
public CosmosContainsTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// 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 EqualsTranslator : IMethodCallTranslator
public class CosmosEqualsTranslator : IMethodCallTranslator
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;

Expand All @@ -19,7 +19,7 @@ public class EqualsTranslator : IMethodCallTranslator
/// 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 EqualsTranslator(ISqlExpressionFactory sqlExpressionFactory)
public CosmosEqualsTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// 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 MathTranslator : IMethodCallTranslator
public class CosmosMathTranslator : IMethodCallTranslator
{
private static readonly Dictionary<MethodInfo, string> SupportedMethodTranslations = new()
{
Expand Down Expand Up @@ -77,7 +77,7 @@ public class MathTranslator : IMethodCallTranslator
/// 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 MathTranslator(ISqlExpressionFactory sqlExpressionFactory)
public CosmosMathTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public CosmosMethodCallTranslatorProvider(
_translators.AddRange(
new IMethodCallTranslator[]
{
new EqualsTranslator(sqlExpressionFactory),
new CosmosEqualsTranslator(sqlExpressionFactory),
new CosmosStringMethodTranslator(sqlExpressionFactory),
new ContainsTranslator(sqlExpressionFactory),
new RandomTranslator(sqlExpressionFactory),
new MathTranslator(sqlExpressionFactory),
new RegexMethodTranslator(sqlExpressionFactory)
new CosmosContainsTranslator(sqlExpressionFactory),
new CosmosRandomTranslator(sqlExpressionFactory),
new CosmosMathTranslator(sqlExpressionFactory),
new CosmosRegexTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
//new GetValueOrDefaultTranslator(sqlExpressionFactory),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// 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
public class CosmosRandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo MethodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(
nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
Expand All @@ -24,7 +24,7 @@ public class RandomTranslator : IMethodCallTranslator
/// 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(ISqlExpressionFactory sqlExpressionFactory)
public CosmosRandomTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,15 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// 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 RegexMethodTranslator : IMethodCallTranslator
public class CosmosRegexTranslator : IMethodCallTranslator
{
private static readonly MethodInfo IsMatch =
typeof(Regex).GetRuntimeMethod(nameof(Regex.IsMatch), new[] { typeof(string), typeof(string) })!;

private static readonly MethodInfo IsMatchWithRegexOptions =
typeof(Regex).GetRuntimeMethod(nameof(Regex.IsMatch), new[] { typeof(string), typeof(string), typeof(RegexOptions) })!;

private static readonly ISet<RegexOptions> AllowedOptions = new HashSet<RegexOptions>
{
RegexOptions.None,
RegexOptions.IgnoreCase,
RegexOptions.Multiline,
RegexOptions.Singleline,
RegexOptions.IgnorePatternWhitespace
};
private const RegexOptions SupportedOptions = RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace;

private readonly ISqlExpressionFactory _sqlExpressionFactory;

Expand All @@ -36,7 +29,7 @@ public class RegexMethodTranslator : IMethodCallTranslator
/// 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 RegexMethodTranslator(ISqlExpressionFactory sqlExpressionFactory)
public CosmosRegexTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand All @@ -58,37 +51,52 @@ public RegexMethodTranslator(ISqlExpressionFactory sqlExpressionFactory)
return null;
}

RegexOptions options;
var (input, pattern) = (arguments[0], arguments[1]);
var typeMapping = ExpressionExtensions.InferTypeMapping(input, pattern);

if (method == IsMatch)
{
options = RegexOptions.None;
return _sqlExpressionFactory.Function(
"RegexMatch",
new[] {
_sqlExpressionFactory.ApplyTypeMapping(input, typeMapping),
_sqlExpressionFactory.ApplyTypeMapping(pattern, typeMapping)
},
typeof(bool));
}
else if (arguments[2] is SqlConstantExpression { Value: RegexOptions regexOptions }
&& AllowedOptions.Contains(regexOptions))
else if (arguments[2] is SqlConstantExpression { Value: RegexOptions regexOptions })
{
options = regexOptions;
}
else
{
return null;
}

string modifier = options switch
{
RegexOptions.Multiline => "m",
RegexOptions.Singleline => "s",
RegexOptions.IgnoreCase => "i",
RegexOptions.IgnorePatternWhitespace => "x",
_ => ""
};
string modifier = "";
if (regexOptions.HasFlag(RegexOptions.Multiline))
{
modifier += "m";
}
if (regexOptions.HasFlag(RegexOptions.Singleline))
{
modifier += "s";
}
if (regexOptions.HasFlag(RegexOptions.IgnoreCase))
{
modifier += "i";
}
if (regexOptions.HasFlag(RegexOptions.IgnorePatternWhitespace))
{
modifier += "x";
}

var (input, pattern) = (arguments[0], arguments[1]);
var stringTypeMapping = ExpressionExtensions.InferTypeMapping(input, pattern);
return (regexOptions & ~SupportedOptions) == 0
? _sqlExpressionFactory.Function(
"RegexMatch",
new[]
{
_sqlExpressionFactory.ApplyTypeMapping(input, typeMapping),
_sqlExpressionFactory.ApplyTypeMapping(pattern, typeMapping),
_sqlExpressionFactory.Constant(modifier, typeMapping)
},
typeof(bool))
: null;
}

return _sqlExpressionFactory.Function(
"RegexMatch",
new[] { input, pattern, _sqlExpressionFactory.Constant(modifier) },
method.ReturnType,
stringTypeMapping);
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ public override async Task Regex_IsMatch_MethodCall(bool async)
AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND RegexMatch(c[""CustomerID""], ""^T"", """"))");
WHERE ((c[""Discriminator""] = ""Customer"") AND RegexMatch(c[""CustomerID""], ""^T""))");
}

public override async Task Regex_IsMatch_MethodCall_constant_input(bool async)
Expand All @@ -1220,7 +1220,7 @@ public override async Task Regex_IsMatch_MethodCall_constant_input(bool async)
AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND RegexMatch(""ALFKI"", c[""CustomerID""], """"))");
WHERE ((c[""Discriminator""] = ""Customer"") AND RegexMatch(""ALFKI"", c[""CustomerID""]))");
}

[ConditionalTheory]
Expand Down Expand Up @@ -1298,11 +1298,31 @@ FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND RegexMatch(c[""CustomerID""], ""^T"", ""x""))");
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Regex_IsMatch_MethodCall_With_Options_IgnoreCase_And_IgnorePatternWhitespace(bool async)
{
await AssertQuery(
async,
ss => ss.Set<Customer>().Where(o => Regex.IsMatch(o.CustomerID, "^T", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace)),
entryCount: 6);

AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND RegexMatch(c[""CustomerID""], ""^T"", ""ix""))");
}

[Fact]
public void Regex_IsMatchOptionsUnsupported()
public virtual void Regex_IsMatch_MethodCall_With_Unsupported_Option()
=> Assert.Throws<InvalidOperationException>(() =>
Fixture.CreateContext().Customers.Where(o => Regex.IsMatch(o.CustomerID, "^T", RegexOptions.RightToLeft)).ToList());

[Fact]
public virtual void Regex_IsMatch_MethodCall_With_Any_Unsupported_Option()
=> Assert.Throws<InvalidOperationException>(() =>
Fixture.CreateContext().Customers.Where(o => Regex.IsMatch(o.CustomerID, "^T", RegexOptions.IgnoreCase | RegexOptions.RightToLeft)).ToList());

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Case_insensitive_string_comparison_instance(bool async)
Expand Down

0 comments on commit 4e8da44

Please sign in to comment.