Skip to content

Commit

Permalink
Fix expression constant in contains (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
zulander1 committed Oct 26, 2023
1 parent 968fa76 commit ffb3a40
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 3 deletions.
45 changes: 45 additions & 0 deletions src/Redis.OM/Common/ExpressionParserUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,12 @@ private static string TranslateContainsStandardQuerySyntax(MethodCallExpression
searchFieldAttribute = DetermineSearchAttribute(expression);
}

if (exp.Arguments.LastOrDefault() is MemberExpression memEx && exp.Arguments.FirstOrDefault() is ConstantExpression cs)
{
var propertyName = $"{GetOperandString(memEx)}";
return $"({GetContainsStringForConstantExpression(propertyName, cs)})";
}

if (expression == null)
{
throw new InvalidOperationException($"Could not parse query for Contains");
Expand All @@ -885,6 +891,45 @@ private static string TranslateContainsStandardQuerySyntax(MethodCallExpression
return (type == typeof(string)) ? $"({memberName}:{{*{EscapeTagField(literal)}*}})" : $"({memberName}:{{{EscapeTagField(literal)}}})";
}

private static string GetContainsStringForConstantExpression(string propertyNameOperand, ConstantExpression cs)
{
var enumerable = cs.Value as IEnumerable;
if (enumerable is null)
{
throw new ArgumentException("Could not create contains predicate from non-enumerable value");
}

var isNumeric = TypeDeterminationUtilities.IsNumericEnumerable(enumerable);
var sb = new StringBuilder();

if (!isNumeric)
{
sb.Append($"{propertyNameOperand}:{{");
}

foreach (var o in enumerable)
{
if (isNumeric)
{
sb.Append($"{propertyNameOperand}:[{o} {o}]");
}
else
{
sb.Append(EscapeTagField(o.ToString()));
}

sb.Append("|");
}

sb.Remove(sb.Length - 1, 1);
if (!isNumeric)
{
sb.Append("}");
}

return sb.ToString();
}

private static string TranslateAnyForEmbeddedObjects(MethodCallExpression exp)
{
var type = exp.Arguments.Last().Type;
Expand Down
18 changes: 18 additions & 0 deletions src/Redis.OM/Modeling/TypeDeterminationUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -27,6 +28,23 @@ internal static class TypeDeterminationUtilities
typeof(DateTime),
};

/// <summary>
/// Determins whether the collection is a numeric collection.
/// </summary>
/// <param name="enumerable">The enumerable.</param>
/// <returns>whether the enumerable is a numeric enumerable.</returns>
internal static bool IsNumericEnumerable(in IEnumerable enumerable)
{
var type = enumerable.GetType().IsArray ? enumerable.GetType().GetElementType() : enumerable.GetType().GenericTypeArguments[0];
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}

var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
return NumericTypes.Contains(underlyingType);
}

/// <summary>
/// Is the type numeric.
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions src/Redis.OM/Redis.OM.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<RootNamespace>Redis.OM</RootNamespace>
<Nullable>enable</Nullable>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<PackageVersion>0.5.3</PackageVersion>
<Version>0.5.3</Version>
<PackageReleaseNotes>https://github.com/redis/redis-om-dotnet/releases/tag/v0.5.3</PackageReleaseNotes>
<PackageVersion>0.5.4</PackageVersion>
<Version>0.5.4</Version>
<PackageReleaseNotes>https://github.com/redis/redis-om-dotnet/releases/tag/v0.5.4</PackageReleaseNotes>
<Description>Object Mapping and More for Redis</Description>
<Title>Redis OM</Title>
<Authors>Steve Lorello</Authors>
Expand Down
18 changes: 18 additions & 0 deletions test/Redis.OM.Unit.Tests/RediSearchTests/SearchTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using NSubstitute;
using NSubstitute.ClearExtensions;
Expand Down Expand Up @@ -3428,5 +3430,21 @@ public void NonNullableNumericFieldContains()
"0",
"100");
}

[Fact]
public void TestConstantExpressionContains()
{
_substitute.ClearSubstitute();
_substitute.Execute(Arg.Any<string>(), Arg.Any<string[]>()).Returns(_mockReply);
var collection = new RedisCollection<Person>(_substitute);
var parameter = Expression.Parameter(typeof(Person), "b");
var property = Expression.Property(parameter, "TagField");
var values = new string[] { "James", "Bond" };
MethodInfo contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name.Contains(nameof(Enumerable.Contains))).Single(x => x.GetParameters().Length == 2).MakeGenericMethod(property.Type);
var body = Expression.Call(contains, Expression.Constant(values), property);
var lambada = Expression.Lambda<Func<Person, bool>>(body, parameter);
_ = collection.Where(lambada).ToList();
_substitute.Received().Execute("FT.SEARCH", "person-idx", "(@TagField:{James|Bond})", "LIMIT", "0", "100");
}
}
}

0 comments on commit ffb3a40

Please sign in to comment.