Skip to content

Commit

Permalink
Add ToQueryString method to RedisCollection, RedisAggregationSet (#487)
Browse files Browse the repository at this point in the history
* Add ToQueryString Method

* wrap all parameters in double-quotes
  • Loading branch information
tgmoore authored Oct 4, 2024
1 parent 93e9fd5 commit 9f8a947
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ We'd love your contributions! If you want to contribute please read our [Contrib
* [@CormacLennon](https://github.com/CormacLennon)
* [@ahmedisam99](https://github.com/ahmedisam99)
* [@kirollosonsi](https://github.com/kirollosonsi)
* [@tgmoore](https://github.com/tgmoore)
<!-- Logo -->
[Logo]: images/logo.svg
Expand Down
22 changes: 22 additions & 0 deletions src/Redis.OM/Aggregation/RedisAggregationSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Redis.OM.Common;
using Redis.OM.Contracts;
using Redis.OM.Modeling;
using Redis.OM.Searching;
Expand Down Expand Up @@ -115,6 +116,27 @@ public async ValueTask<List<AggregationResult<T>>> ToListAsync()
public async ValueTask<AggregationResult<T>[]> ToArrayAsync()
=> (await ToListAsync()).ToArray();

/// <summary>
/// A string representation of the aggregation command and parameters, a serialization of the Expression with all parameters explicitly quoted.
/// Warning: this string may not be suitable for direct execution and is intended only for use in debugging.
/// </summary>
/// <returns>A string representing the Expression serialized to an aggregation command and parameters.</returns>
public string ToQueryString()
{
var serializedArgs = ExpressionTranslator.BuildAggregationFromExpression(Expression, typeof(T)).Serialize().ToList();

if (_useCursor)
{
serializedArgs.Add("WITHCURSOR");
serializedArgs.Add("COUNT");
serializedArgs.Add(_chunkSize.ToString());
}

var quotedArgs = serializedArgs.Select(arg => $"\"{arg}\"");

return $"\"FT.AGGREGATE\" {string.Join(" ", quotedArgs)}";
}

private void Initialize(RedisQueryProvider provider, Expression? expression, bool useCursor)
{
Provider = provider ?? throw new ArgumentNullException(nameof(provider));
Expand Down
7 changes: 7 additions & 0 deletions src/Redis.OM/Searching/IRedisCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,12 @@ public interface IRedisCollection<T> : IOrderedQueryable<T>, IAsyncEnumerable<T>
/// <param name="ids">The Ids to look up.</param>
/// <returns>A dictionary correlating the ids provided to the objects in Redis.</returns>
Task<IDictionary<string, T?>> FindByIdsAsync(IEnumerable<string> ids);

/// <summary>
/// A string representation of the Redisearch command and parameters, a serialization of the Expression with all parameters explicitly quoted.
/// Warning: this string may not be suitable for direct execution and is intended only for use in debugging.
/// </summary>
/// <returns>A string representing the Expression serialized to a Redisearch command and parameters.</returns>
string ToQueryString();
}
}
11 changes: 11 additions & 0 deletions src/Redis.OM/Searching/RedisCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,17 @@ public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToke
return new RedisCollectionEnumerator<T>(Expression, provider.Connection, ChunkSize, StateManager, BooleanExpression, SaveState, RootType, typeof(T));
}

/// <inheritdoc/>
public string ToQueryString()
{
var query = ExpressionTranslator.BuildQueryFromExpression(Expression, typeof(T), BooleanExpression, RootType);
query.Limit ??= new SearchLimit { Number = ChunkSize, Offset = 0 };

var quotedArgs = query.SerializeQuery().Select(arg => $"\"{arg}\"");

return $"\"FT.SEARCH\" {string.Join(" ", quotedArgs)}";
}

private static MethodInfo GetMethodInfo<T1, T2>(Func<T1, T2> f, T1 unused)
{
return f.Method;
Expand Down
18 changes: 18 additions & 0 deletions test/Redis.OM.Unit.Tests/RediSearchTests/AggregationSetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -659,5 +659,23 @@ public async Task TestNoCursorDelete()
await _substitute.Received().ExecuteAsync("FT.AGGREGATE", "person-idx", "*", "FILTER", "@TagField == 'foo' && @Address_State == 'FL'");
await _substitute.DidNotReceive().ExecuteAsync("FT.CURSOR", Arg.Any<object[]>());
}

[Fact]
public void TestToQueryString()
{
var collection = new RedisAggregationSet<Person>(_substitute, true, chunkSize: 10000);
var command = "\"FT.AGGREGATE\" \"person-idx\" \"@Salary:[(30.55 inf]\" \"LOAD\" \"*\" \"APPLY\" \"@Address_HouseNumber + 4\" \"AS\" \"house_num_modified\" \"SORTBY\" \"2\" \"@Age\" \"DESC\" \"LIMIT\" \"0\" \"10\" \"WITHCURSOR\" \"COUNT\" \"10000\"";

var queryString = collection
.LoadAll()
.Apply(x => x.RecordShell.Address.HouseNumber + 4, "house_num_modified")
.Where(a => a.RecordShell!.Salary > 30.55M)
.OrderByDescending(p=>p.RecordShell.Age)
.Skip(0)
.Take(10)
.ToQueryString();

Assert.Equal(command, queryString);
}
}
}
12 changes: 12 additions & 0 deletions test/Redis.OM.Unit.Tests/RediSearchTests/SearchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3889,5 +3889,17 @@ await _substitute.Received().ExecuteAsync("FT.SEARCH",
"0",
"100");
}

[Fact]
public void TestToQueryString()
{
_substitute.ClearSubstitute();
var command = "\"FT.SEARCH\" \"person-idx\" \"(((@Name:Ste) | (@Height:[70 inf])) (@Age:[-inf (33]))\" \"LIMIT\" \"100\" \"10\" \"SORTBY\" \"Age\" \"ASC\"";

var collection = new RedisCollection<Person>(_substitute);
var queryString = collection.Where(x => x.Name.Contains("Ste") || x.Height >= 70).Where(x => x.Age < 33).OrderBy(x => x.Age).Skip(100).Take(10).ToQueryString();

Assert.Equal(command, queryString);
}
}
}

0 comments on commit 9f8a947

Please sign in to comment.