Skip to content

Commit

Permalink
Fixing issues with how selects work with Complex types (#363)
Browse files Browse the repository at this point in the history
* Fixing issues with how selects work with Complex types

* revving version
  • Loading branch information
slorello89 authored May 3, 2023
1 parent dac4774 commit 0e3c4b4
Show file tree
Hide file tree
Showing 19 changed files with 327 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ protected override void ValidateAndPushOperand(Expression expression, Stack<stri
var val = ExpressionParserUtilities.GetOperandStringForQueryArgs(binaryExpression.Right);
stack.Push(BuildQueryPredicate(binaryExpression.NodeType, memberExpression.Member, System.Linq.Expressions.Expression.Constant(val)));
}

}
else if (expression is ConstantExpression c
&& c.Value.ToString() == "*")
Expand Down
5 changes: 4 additions & 1 deletion src/Redis.OM/Aggregation/RedisAggregation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public RedisAggregation(string indexName)
/// <summary>
/// Gets or sets the query predicate.
/// </summary>
public List<QueryPredicate> Queries { get; set; } = new();
public List<QueryPredicate> Queries { get; set; } = new ();

/// <summary>
/// Gets or sets the limit.
/// </summary>
Expand All @@ -56,12 +57,14 @@ public string[] Serialize()
{
queries.AddRange(query.Serialize());
}

ret.AddRange(new[] { string.Join(" ", queries) });
}
else
{
ret.Add("*");
}

foreach (var predicate in Predicates)
{
ret.AddRange(predicate.Serialize());
Expand Down
102 changes: 98 additions & 4 deletions src/Redis.OM/Common/ExpressionTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,10 @@ public static RedisAggregation BuildAggregationFromExpression(Expression express
/// <param name="expression">The expression.</param>
/// <param name="type">The root type.</param>
/// <param name="mainBooleanExpression">The primary boolean expression to build the filter from.</param>
/// <param name="rootType">The root type for the expression.</param>
/// <returns>A Redis query.</returns>
/// <exception cref="InvalidOperationException">Thrown if type is missing indexing.</exception>
internal static RedisQuery BuildQueryFromExpression(Expression expression, Type type, Expression? mainBooleanExpression)
internal static RedisQuery BuildQueryFromExpression(Expression expression, Type type, Expression? mainBooleanExpression, Type rootType)
{
var attr = type.GetCustomAttribute<DocumentAttribute>();
if (attr == null)
Expand Down Expand Up @@ -206,7 +207,7 @@ internal static RedisQuery BuildQueryFromExpression(Expression expression, Type
query.SortBy = TranslateOrderByMethod(exp, false);
break;
case "Select":
query.Return = TranslateSelectMethod(exp);
query.Return = TranslateSelectMethod(exp, rootType, attr);
break;
case "Take":
query.Limit ??= new SearchLimit { Offset = 0 };
Expand Down Expand Up @@ -558,16 +559,109 @@ private static void TranslateAndPushTwoArgumentReductionPredicate(MethodCallExpr

private static int TranslateSkip(MethodCallExpression exp) => (int)((ConstantExpression)exp.Arguments[1]).Value;

private static ReturnFields TranslateSelectMethod(MethodCallExpression expression)
private static string AliasOrPath(Type t, DocumentAttribute attr, MemberExpression expression)
{
if (attr.StorageType == StorageType.Json)
{
var innerMember = expression.Expression as MemberExpression;
if (innerMember != null)
{
Expression innerExpression = innerMember;
var pathStack = new Stack<string>();
pathStack.Push(expression.Member.Name);
while (innerMember != null)
{
pathStack.Push(innerMember.Member.Name);
innerMember = innerMember.Expression as MemberExpression;
}

return $"$.{string.Join(".", pathStack)}";
}

if (expression.Member.DeclaringType != null && RedisSchemaField.IsComplexType(expression.Type))
{
return $"$.{expression.Member.Name}"; // this can't have been aliased so return a path to it.
}

var searchField = expression.Member.GetCustomAttributes(typeof(SearchFieldAttribute)).FirstOrDefault();
if (searchField != default)
{
return expression.Member.Name;
}

return $"$.{expression.Member.Name}";
}
else
{
return expression.Member.Name;
}
}

private static ReturnFields TranslateSelectMethod(MethodCallExpression expression, Type t, DocumentAttribute attr)
{
var predicate = (UnaryExpression)expression.Arguments[1];
var lambda = (LambdaExpression)predicate.Operand;

if (lambda.Body is MemberExpression member)
{
var properties = new[] { member.Member.Name };
var properties = new[] { AliasOrPath(t, attr, member) };
return new ReturnFields(properties);
}

if (lambda.Body is MemberInitExpression memberInitExpression)
{
var returnFields = new List<ReturnField>();
foreach (var binding in memberInitExpression.Bindings)
{
if (binding is MemberAssignment assignment)
{
if (assignment.Expression is MemberExpression assignmentExpression)
{
var path = AliasOrPath(t, attr, assignmentExpression);
if (assignmentExpression.Member.Name == binding.Member.Name)
{
returnFields.Add(new (path));
}
else
{
returnFields.Add(new (path, binding.Member.Name));
}
}
}
}

return new ReturnFields(returnFields);
}

if (lambda.Body is NewExpression newExpression)
{
var returnFields = new List<ReturnField>();
if (newExpression.Members.Count != newExpression.Arguments.Count())
{
throw new ArgumentException(
"Could not parse Select predicate because of the shape of the new expresssion");
}

for (var i = 0; i < newExpression.Arguments.Count; i++)
{
var newExpressionArg = newExpression.Arguments[i];
var memberInfo = newExpression.Members[i];
if (newExpressionArg is MemberExpression newExpressionMember)
{
var path = AliasOrPath(t, attr, newExpressionMember);
if (newExpressionMember.Member.Name == memberInfo.Name)
{
returnFields.Add(new ReturnField(path, memberInfo.Name));
}
else
{
returnFields.Add(new ReturnField(path, memberInfo.Name));
}
}
}

return new ReturnFields(returnFields);
}
else
{
var properties = lambda.ReturnType.GetProperties().Select(x => x.Name);
Expand Down
3 changes: 1 addition & 2 deletions src/Redis.OM/Modeling/JsonDiff.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Newtonsoft.Json;
using System.Web;
using System.Web;
using Newtonsoft.Json.Linq;

namespace Redis.OM.Modeling
Expand Down
5 changes: 5 additions & 0 deletions src/Redis.OM/Modeling/RedisSchemaField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ namespace Redis.OM.Modeling
/// </summary>
internal static class RedisSchemaField
{
/// <summary>
/// Checks the type to see if it's a complex type that cannot be used as a scalar in RediSearch.
/// </summary>
/// <param name="type">The Type to check.</param>
/// <returns>Whether not we consider the type to be complex.</returns>
internal static bool IsComplexType(Type type)
{
return !TypeDeterminationUtilities.IsNumeric(type)
Expand Down
7 changes: 4 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.0</PackageVersion>
<Version>0.5.0</Version>
<PackageReleaseNotes>https://github.com/redis/redis-om-dotnet/releases/tag/v0.5.0</PackageReleaseNotes>
<PackageVersion>0.5.1</PackageVersion>
<Version>0.5.1</Version>
<PackageReleaseNotes>https://github.com/redis/redis-om-dotnet/releases/tag/v0.5.1</PackageReleaseNotes>
<Description>Object Mapping and More for Redis</Description>
<Title>Redis OM</Title>
<Authors>Steve Lorello</Authors>
Expand All @@ -21,6 +21,7 @@
<RepositoryType>Github</RepositoryType>
<PackageTags>redis redisearch indexing databases</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
Expand Down
10 changes: 10 additions & 0 deletions src/Redis.OM/RedisObjectHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ internal static T FromHashSet<T>(IDictionary<string, string> hash)
{
asJson = hash["$"];
}
else if (hash.Keys.Count > 0 && hash.Keys.All(x => x.StartsWith("$")))
{
asJson = hash.Values.First();
}
else
{
asJson = SendToJson(hash, typeof(T));
Expand Down Expand Up @@ -500,6 +504,12 @@ private static string SendToJson(IDictionary<string, string> hash, Type t)
ret += SendToJson(entries, type);
ret += ",";
}
else if (hash.ContainsKey(propertyName))
{
ret += $"\"{propertyName}\":";
ret += hash[propertyName];
ret += ",";
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Redis.OM/Scripts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ local second_op
/// <summary>
/// The scripts.
/// </summary>
internal static readonly Dictionary<string, string> ScriptCollection = new()
internal static readonly Dictionary<string, string> ScriptCollection = new ()
{
{ nameof(JsonDiffResolution), JsonDiffResolution },
{ nameof(HashDiffResolution), HashDiffResolution },
Expand All @@ -170,6 +170,6 @@ local second_op
/// <summary>
/// Gets or sets collection of SHAs.
/// </summary>
internal static ConcurrentDictionary<string, string> ShaCollection { get; set; } = new();
internal static ConcurrentDictionary<string, string> ShaCollection { get; set; } = new ();
}
}
4 changes: 3 additions & 1 deletion src/Redis.OM/SearchExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ public static IRedisCollection<TR> Select<T, TR>(this IRedisCollection<T> source
null,
GetMethodInfo(Select, source, expression),
new[] { source.Expression, Expression.Quote(expression) });
return new RedisCollection<TR>((RedisQueryProvider)source.Provider, exp, source.StateManager, null, source.SaveState, source.ChunkSize);
var res = new RedisCollection<TR>((RedisQueryProvider)source.Provider, exp, source.StateManager, null, source.SaveState, source.ChunkSize);
res.RootType = typeof(T);
return res;
}

/// <summary>
Expand Down
39 changes: 39 additions & 0 deletions src/Redis.OM/Searching/Query/ReturnField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Redis.OM.Searching.Query
{
/// <summary>
/// Represents a return field with a name and an optional alias.
/// </summary>
public struct ReturnField
{
/// <summary>
/// The name of the return field.
/// </summary>
public string Name;

/// <summary>
/// An optional alias for the return field.
/// </summary>
public string? Alias;

/// <summary>
/// Initializes a new instance of the <see cref="ReturnField"/> struct with the specified name and alias.
/// </summary>
/// <param name="name">The name of the return field.</param>
/// <param name="alias">An optional alias for the return field.</param>
public ReturnField(string name, string alias)
{
Name = name;
Alias = alias;
}

/// <summary>
/// Initializes a new instance of the <see cref="ReturnField"/> struct with the specified name.
/// </summary>
/// <param name="name">The name of the return field.</param>
public ReturnField(string name)
{
Name = name;
Alias = null;
}
}
}
23 changes: 19 additions & 4 deletions src/Redis.OM/Searching/Query/ReturnFields.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Redis.OM.Searching.Query
Expand All @@ -11,13 +12,22 @@ public class ReturnFields : QueryOption
/// <summary>
/// The fields to bring back.
/// </summary>
private readonly IEnumerable<string> _fields;
private readonly IEnumerable<ReturnField> _fields;

/// <summary>
/// Initializes a new instance of the <see cref="ReturnFields"/> class.
/// </summary>
/// <param name="fields">the fields to return.</param>
public ReturnFields(IEnumerable<string> fields)
{
_fields = fields.Select(x => new ReturnField(x));
}

/// <summary>
/// Initializes a new instance of the <see cref="ReturnFields"/> class.
/// </summary>
/// <param name="fields">the fields to return.</param>
public ReturnFields(IEnumerable<ReturnField> fields)
{
_fields = fields;
}
Expand All @@ -27,10 +37,15 @@ internal override IEnumerable<string> SerializeArgs
{
get
{
var ret = new List<string> { "RETURN", _fields.Count().ToString() };
var ret = new List<string> { "RETURN", (_fields.Count() + (_fields.Count(x => x.Alias != null) * 2)).ToString() };
foreach (var field in _fields)
{
ret.Add($"{field}");
ret.Add($"{field.Name}");
if (field.Alias != null)
{
ret.Add("AS");
ret.Add(field.Alias);
}
}

return ret.ToArray();
Expand Down
Loading

0 comments on commit 0e3c4b4

Please sign in to comment.