Skip to content

Commit 8f0e204

Browse files
authored
adding proper translation for DateTimeOffsets (#406)
1 parent b0f2b9e commit 8f0e204

File tree

8 files changed

+164
-9
lines changed

8 files changed

+164
-9
lines changed

src/Redis.OM/Aggregation/AggregationPredicates/QueryPredicate.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,21 @@ public IEnumerable<string> Serialize()
4040
/// <inheritdoc/>
4141
protected override void ValidateAndPushOperand(Expression expression, Stack<string> stack)
4242
{
43-
if (expression is BinaryExpression binaryExpression
44-
&& binaryExpression.Left is MemberExpression memberExpression)
43+
if (expression is BinaryExpression binaryExpression)
4544
{
45+
var memberExpression = binaryExpression.Left as MemberExpression;
46+
if (memberExpression is null)
47+
{
48+
if (binaryExpression.Left is UnaryExpression { NodeType: ExpressionType.Convert, Operand: MemberExpression } leftUnary)
49+
{
50+
memberExpression = (MemberExpression)leftUnary.Operand;
51+
}
52+
else
53+
{
54+
throw new InvalidOperationException("Invalid Expression Type");
55+
}
56+
}
57+
4658
if (binaryExpression.Right is ConstantExpression constantExpression)
4759
{
4860
stack.Push(BuildQueryPredicate(binaryExpression.NodeType, memberExpression, constantExpression));
@@ -54,17 +66,17 @@ protected override void ValidateAndPushOperand(Expression expression, Stack<stri
5466
case ConstantExpression c:
5567
stack.Push(BuildQueryPredicate(binaryExpression.NodeType, memberExpression, c));
5668
break;
57-
case MemberExpression mem when mem.Expression is ConstantExpression frame:
69+
case MemberExpression mem:
5870
{
59-
var val = ExpressionParserUtilities.GetValue(mem.Member, frame.Value);
71+
var val = ExpressionParserUtilities.GetOperandString(mem);
6072
stack.Push(BuildQueryPredicate(binaryExpression.NodeType, memberExpression, System.Linq.Expressions.Expression.Constant(val)));
6173
break;
6274
}
6375
}
6476
}
65-
else if (binaryExpression.Right is MemberExpression mem && mem.Expression is ConstantExpression frame)
77+
else if (binaryExpression.Right is MemberExpression mem)
6678
{
67-
var val = ExpressionParserUtilities.GetValue(mem.Member, frame.Value);
79+
var val = ExpressionParserUtilities.GetOperandString(mem);
6880
stack.Push(BuildQueryPredicate(binaryExpression.NodeType, memberExpression, System.Linq.Expressions.Expression.Constant(val)));
6981
}
7082
else

src/Redis.OM/Common/ExpressionParserUtilities.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,11 @@ private static string ValueToString(object value)
903903
return ((double)value).ToString(CultureInfo.InvariantCulture);
904904
}
905905

906+
if (value is DateTimeOffset dto)
907+
{
908+
return dto.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
909+
}
910+
906911
if (value is DateTime dt)
907912
{
908913
return new DateTimeOffset(dt).ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);

test/Redis.OM.Unit.Tests/ConnectionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public void TestConnectStandalone()
2323
Assert.Equal("Bar",res);
2424
}
2525

26-
[Fact]
26+
[SkipIfMissingEnvVar("SENTINLE_HOST_PORT")]
2727
public void TestSentinel()
2828
{
2929
var hostInfo = System.Environment.GetEnvironmentVariable("SENTINLE_HOST_PORT") ?? "localhost:26379";
@@ -63,7 +63,7 @@ public void GivenMultiplexerConnection_WhenTestingSetCommand_ThenShouldExecuteSe
6363
Assert.Equal("Bar", res);
6464
}
6565

66-
[Fact]
66+
[SkipIfMissingEnvVar("PRIVATE_HOST", "PRIVATE_PORT", "PRIVATE_PASSWORD")]
6767
public void TestPrivateConnection()
6868
{
6969
var host = Environment.GetEnvironmentVariable("PRIVATE_HOST") ?? "redis-private";

test/Redis.OM.Unit.Tests/CoreTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ public void CommandTest()
454454
stream.Flush();
455455
}
456456

457-
[Fact]
457+
[SkipIfMissingEnvVar("AGGRESSIVELY_SHORT_TIMEOUT_REDIS")]
458458
public async Task SearchTimeoutTest()
459459
{
460460
var hostInfo = Environment.GetEnvironmentVariable("AGGRESSIVELY_SHORT_TIMEOUT_REDIS") ?? string.Empty;

test/Redis.OM.Unit.Tests/RediSearchTests/AggregationSetTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,34 @@ public void CustomPropertyNamesInQuery()
558558
"COUNT",
559559
"10");
560560
}
561+
562+
[Fact]
563+
public void DateTimeQuery()
564+
{
565+
var dt = DateTime.Now;
566+
var dtMs = new DateTimeOffset(dt).ToUnixTimeMilliseconds();
567+
568+
var dto = DateTimeOffset.Now.Subtract(TimeSpan.FromHours(3));
569+
var dtoMs = dto.ToUnixTimeMilliseconds();
570+
var collection = new RedisAggregationSet<ObjectWithDateTime>(_substitute, true, chunkSize: 10000);
571+
_substitute.Execute("FT.AGGREGATE", Arg.Any<string[]>()).Returns(MockedResult);
572+
_substitute.Execute("FT.CURSOR", Arg.Any<string[]>()).Returns(MockedResultCursorEnd);
573+
574+
Expression<Func<AggregationResult<ObjectWithDateTime>, bool>> query = a => a.RecordShell.Timestamp > dt;
575+
_ = collection.Where(query).ToList();
576+
_substitute.Received().Execute("FT.AGGREGATE", "objectwithdatetime-idx", $"@Timestamp:[({dtMs} inf]", "WITHCURSOR", "COUNT", "10000");
577+
578+
query = a => a.RecordShell.Timestamp > dto;
579+
_ = collection.Where(query).ToList();
580+
_substitute.Received().Execute("FT.AGGREGATE", "objectwithdatetime-idx", $"@Timestamp:[({dtoMs} inf]", "WITHCURSOR", "COUNT", "10000");
561581

582+
query = a => a.RecordShell.TimestampOffset > dto;
583+
_ = collection.Where(query).ToList();
584+
_substitute.Received().Execute("FT.AGGREGATE", "objectwithdatetime-idx", $"@TimestampOffset:[({dtoMs} inf]", "WITHCURSOR", "COUNT", "10000");
585+
586+
query = a => a.RecordShell.TimestampOffset > dt;
587+
_ = collection.Where(query).ToList();
588+
_substitute.Received().Execute("FT.AGGREGATE", "objectwithdatetime-idx", $"@TimestampOffset:[({dtMs} inf]", "WITHCURSOR", "COUNT", "10000");
589+
}
562590
}
563591
}

test/Redis.OM.Unit.Tests/RediSearchTests/ObjectWithDateTime.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class ObjectWithDateTime
1010
public string Id { get; set; }
1111
[Indexed(Sortable = true)]
1212
public DateTime Timestamp { get; set; }
13+
[Indexed(Sortable = true)]
14+
public DateTimeOffset TimestampOffset { get; set; }
1315
[Indexed]
1416
public DateTime? NullableTimestamp { get; set; }
1517
}

test/Redis.OM.Unit.Tests/RediSearchTests/SearchTests.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,6 +2387,84 @@ public void Issue201()
23872387
);
23882388
}
23892389

2390+
[Fact]
2391+
public void RangeOnDateTimeWithMultiplePredicates()
2392+
{
2393+
_substitute.ClearSubstitute();
2394+
_substitute.Execute(Arg.Any<string>(), Arg.Any<string[]>()).Returns(_mockReply);
2395+
2396+
var fromDto = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(4));
2397+
var toDto = DateTimeOffset.UtcNow;
2398+
2399+
var fromDt = fromDto.DateTime;
2400+
var toDt = toDto.DateTime;
2401+
2402+
var msFrom = fromDto.ToUnixTimeMilliseconds();
2403+
var msTo = toDto.ToUnixTimeMilliseconds();
2404+
2405+
var collection = new RedisCollection<ObjectWithDateTime>(_substitute, 1000);
2406+
_ = collection.Where(x => x.TimestampOffset <= fromDto && x.TimestampOffset >= toDto).ToList();
2407+
_ = collection.Where(x => x.TimestampOffset <= fromDt && x.TimestampOffset >= toDt).ToList();
2408+
_ = collection.Where(x => x.Timestamp <= fromDto && x.Timestamp >= toDto).ToList();
2409+
_ = collection.Where(x => x.Timestamp <= fromDt && x.Timestamp >= toDt).ToList();
2410+
_ = collection.Where(x => x.NullableTimestamp <= fromDto && x.NullableTimestamp >= toDto).ToList();
2411+
_ = collection.Where(x => x.NullableTimestamp <= fromDt && x.NullableTimestamp >= toDt).ToList();
2412+
2413+
_substitute.Received().Execute(
2414+
"FT.SEARCH",
2415+
"objectwithdatetime-idx",
2416+
$"((@TimestampOffset:[-inf {msFrom}]) (@TimestampOffset:[{msTo} inf]))",
2417+
"LIMIT",
2418+
"0",
2419+
"1000"
2420+
);
2421+
2422+
_substitute.Received().Execute(
2423+
"FT.SEARCH",
2424+
"objectwithdatetime-idx",
2425+
$"((@TimestampOffset:[-inf {new DateTimeOffset(fromDt).ToUnixTimeMilliseconds()}]) (@TimestampOffset:[{new DateTimeOffset(toDt).ToUnixTimeMilliseconds()} inf]))",
2426+
"LIMIT",
2427+
"0",
2428+
"1000"
2429+
);
2430+
2431+
_substitute.Received().Execute(
2432+
"FT.SEARCH",
2433+
"objectwithdatetime-idx",
2434+
$"((@Timestamp:[-inf {msFrom}]) (@Timestamp:[{msTo} inf]))",
2435+
"LIMIT",
2436+
"0",
2437+
"1000"
2438+
);
2439+
2440+
_substitute.Received().Execute(
2441+
"FT.SEARCH",
2442+
"objectwithdatetime-idx",
2443+
$"((@Timestamp:[-inf {new DateTimeOffset(fromDt).ToUnixTimeMilliseconds()}]) (@Timestamp:[{new DateTimeOffset(toDt).ToUnixTimeMilliseconds()} inf]))",
2444+
"LIMIT",
2445+
"0",
2446+
"1000"
2447+
);
2448+
2449+
_substitute.Received().Execute(
2450+
"FT.SEARCH",
2451+
"objectwithdatetime-idx",
2452+
$"((@NullableTimestamp:[-inf {msFrom}]) (@NullableTimestamp:[{msTo} inf]))",
2453+
"LIMIT",
2454+
"0",
2455+
"1000"
2456+
);
2457+
2458+
_substitute.Received().Execute(
2459+
"FT.SEARCH",
2460+
"objectwithdatetime-idx",
2461+
$"((@NullableTimestamp:[-inf {new DateTimeOffset(fromDt).ToUnixTimeMilliseconds()}]) (@NullableTimestamp:[{new DateTimeOffset(toDt).ToUnixTimeMilliseconds()} inf]))",
2462+
"LIMIT",
2463+
"0",
2464+
"1000"
2465+
);
2466+
}
2467+
23902468
[Fact]
23912469
public void RangeOnDatetime()
23922470
{
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Linq;
3+
using Xunit;
4+
5+
namespace Redis.OM.Unit.Tests;
6+
7+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
8+
public class SkipIfMissingEnvVarAttribute : FactAttribute
9+
{
10+
private readonly string[] _envVars;
11+
12+
public SkipIfMissingEnvVarAttribute(params string[] envVars)
13+
{
14+
_envVars = envVars;
15+
}
16+
17+
public override string Skip
18+
{
19+
get
20+
{
21+
var missingEnvVars = _envVars.Where(x => Environment.GetEnvironmentVariable(x) == null).ToArray();
22+
if (missingEnvVars.Any())
23+
{
24+
return $"Skipping because the following environment variables were missing: {string.Join(",", missingEnvVars)}";
25+
}
26+
27+
return null;
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)