From 48ea85efa69693d6d7430c58a47c5aece43f85f1 Mon Sep 17 00:00:00 2001 From: Steve Lorello <42971704+slorello89@users.noreply.github.com> Date: Mon, 28 Oct 2024 07:55:35 -0400 Subject: [PATCH] fixing issue with timespans having fractional millisecond values (#497) * fixing issue with timespans having fractional values * fixign test * fixing test * more test fixes --- src/Redis.OM/Extensions/TimespanExtensions.cs | 20 ++++++++++++++ src/Redis.OM/RedisCommands.cs | 20 +++++++------- src/Redis.OM/Searching/RedisCollection.cs | 12 ++++----- test/Redis.OM.Unit.Tests/CoreTests.cs | 4 +-- .../RediSearchTests/SearchFunctionalTests.cs | 26 +++++++++++++++++++ 5 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 src/Redis.OM/Extensions/TimespanExtensions.cs diff --git a/src/Redis.OM/Extensions/TimespanExtensions.cs b/src/Redis.OM/Extensions/TimespanExtensions.cs new file mode 100644 index 0000000..7c83225 --- /dev/null +++ b/src/Redis.OM/Extensions/TimespanExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; + +namespace Redis.OM; + +/// +/// Extension methods for Timespans. +/// +internal static class TimespanExtensions +{ + /// + /// Rounds up total milliseconds as an integer. + /// + /// the timespan. + /// the rounded timespan milliseconds. + public static string TotalMillisecondsString(this TimeSpan ts) + { + return Math.Ceiling(ts.TotalMilliseconds).ToString(CultureInfo.InvariantCulture); + } +} \ No newline at end of file diff --git a/src/Redis.OM/RedisCommands.cs b/src/Redis.OM/RedisCommands.cs index 2f10a2f..098c283 100644 --- a/src/Redis.OM/RedisCommands.cs +++ b/src/Redis.OM/RedisCommands.cs @@ -187,7 +187,7 @@ public static async Task JsonSetAsync(this IRedisConnection connection, st /// whether the operation succeeded. public static async Task JsonSetAsync(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null) { - var argList = new List { timeSpan != null ? ((long)timeSpan.Value.TotalMilliseconds).ToString() : "-1", path, json }; + var argList = new List { timeSpan != null ? timeSpan.Value.TotalMillisecondsString() : "-1", path, json }; switch (when) { case WhenKey.Exists: @@ -328,7 +328,7 @@ public static bool JsonSet(this IRedisConnection connection, string key, string /// whether the operation succeeded. public static bool JsonSet(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null) { - var argList = new List { timeSpan != null ? ((long)timeSpan.Value.TotalMilliseconds).ToString() : "-1", path, json }; + var argList = new List { timeSpan != null ? timeSpan.Value.TotalMillisecondsString() : "-1", path, json }; switch (when) { case WhenKey.Exists: @@ -416,7 +416,7 @@ public static string Set(this IRedisConnection connection, object obj) var kvps = obj.BuildHashSet(); var argsList = new List(); int? res = null; - argsList.Add(timespan != null ? ((long)timespan.Value.TotalMilliseconds).ToString() : "-1"); + argsList.Add(timespan != null ? timespan.Value.TotalMillisecondsString() : "-1"); foreach (var kvp in kvps) { argsList.Add(kvp.Key); @@ -467,7 +467,7 @@ public static string Set(this IRedisConnection connection, object obj) var kvps = obj.BuildHashSet(); var argsList = new List(); int? res = null; - argsList.Add(timespan != null ? ((long)timespan.Value.TotalMilliseconds).ToString() : "-1"); + argsList.Add(timespan != null ? timespan.Value.TotalMillisecondsString() : "-1"); foreach (var kvp in kvps) { argsList.Add(kvp.Key); @@ -794,8 +794,8 @@ internal static void UnlinkAndSet(this IRedisConnection connection, string ke args.Add(pair.Value); if (ttl is not null) { - args.Add("EXPIRE"); - args.Add(ttl.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture)); + args.Add("PEXPIRE"); + args.Add(ttl.Value.TotalMillisecondsString()); } } @@ -831,8 +831,8 @@ internal static async Task UnlinkAndSetAsync(this IRedisConnection connection args.Add(pair.Value); if (ttl is not null) { - args.Add("EXPIRE"); - args.Add(ttl.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture)); + args.Add("PEXPIRE"); + args.Add(ttl.Value.TotalMillisecondsString()); } } @@ -848,7 +848,7 @@ private static RedisReply[] SendCommandWithExpiry( TimeSpan ts) { var commandTuple = Tuple.Create(command, args); - var expireTuple = Tuple.Create("PEXPIRE", new object[] { new RedisKey(keyToExpire), ((long)ts.TotalMilliseconds).ToString(CultureInfo.InvariantCulture) }); + var expireTuple = Tuple.Create("PEXPIRE", new object[] { new RedisKey(keyToExpire), ts.TotalMillisecondsString() }); return connection.ExecuteInTransaction(new[] { commandTuple, expireTuple }); } @@ -860,7 +860,7 @@ private static Task SendCommandWithExpiryAsync( TimeSpan ts) { var commandTuple = Tuple.Create(command, args); - var expireTuple = Tuple.Create("PEXPIRE", new object[] { new RedisKey(keyToExpire), ((long)ts.TotalMilliseconds).ToString(CultureInfo.InvariantCulture) }); + var expireTuple = Tuple.Create("PEXPIRE", new object[] { new RedisKey(keyToExpire), ts.TotalMillisecondsString() }); return connection.ExecuteInTransactionAsync(new[] { commandTuple, expireTuple }); } } diff --git a/src/Redis.OM/Searching/RedisCollection.cs b/src/Redis.OM/Searching/RedisCollection.cs index ac3b62c..55ab471 100644 --- a/src/Redis.OM/Searching/RedisCollection.cs +++ b/src/Redis.OM/Searching/RedisCollection.cs @@ -773,14 +773,14 @@ private async Task> UpdateAsyncNoSave(T item, TimeSpan? if (ttl is not null) { args.Add("EXPIRE"); - args.Add(ttl.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture)); + args.Add(ttl.Value.TotalMillisecondsString()); } await _connection.CreateAndEvalAsync(scriptName, new[] { key }, args.ToArray()); } else if (ttl is not null) { - await _connection.ExecuteAsync("PEXPIRE", key, ttl.Value.TotalMilliseconds); + await _connection.ExecuteAsync("PEXPIRE", key, ttl.Value.TotalMillisecondsString()); } } else @@ -841,14 +841,14 @@ private void SendUpdate(T item, TimeSpan? ttl = null) if (ttl is not null) { args.Add("EXPIRE"); - args.Add(ttl.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture)); + args.Add(ttl.Value.TotalMillisecondsString()); } _connection.CreateAndEval(scriptName, new[] { key }, args.ToArray()); } else if (ttl is not null) { - _connection.Execute("PEXPIRE", key, ttl.Value.TotalMilliseconds); + _connection.Execute("PEXPIRE", key, ttl.Value.TotalMillisecondsString()); } } else @@ -879,14 +879,14 @@ private Task SendUpdateAsync(T item, TimeSpan? ttl = null) if (ttl is not null) { args.Add("EXPIRE"); - args.Add(ttl.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture)); + args.Add(ttl.Value.TotalMillisecondsString()); } task = _connection.CreateAndEvalAsync(scriptName, new[] { key }, args.ToArray()); } else if (ttl is not null) { - task = _connection.ExecuteAsync("PEXPIRE", key, ttl.Value.TotalMilliseconds); + task = _connection.ExecuteAsync("PEXPIRE", key, ttl.Value.TotalMillisecondsString()); } } else diff --git a/test/Redis.OM.Unit.Tests/CoreTests.cs b/test/Redis.OM.Unit.Tests/CoreTests.cs index 12fec41..c9e1819 100644 --- a/test/Redis.OM.Unit.Tests/CoreTests.cs +++ b/test/Redis.OM.Unit.Tests/CoreTests.cs @@ -65,7 +65,7 @@ public async Task ExpireFractionalMillisecondAsync() var jsonObjWithExpire = new BasicJsonObject { Name = "JsonWithExpire" }; var key = await connection.SetAsync(jsonObjWithExpire, TimeSpan.FromMilliseconds(5000.5)); var ttl = (long)await connection.ExecuteAsync("PTTL", key); - Assert.True(ttl <= 5000.5); + Assert.True(ttl <= 5001); Assert.True(ttl >= 1000); } @@ -78,7 +78,7 @@ public void ExpireFractionalMillisecond() var jsonObjWithExpire = new BasicJsonObject { Name = "JsonWithExpire" }; var key = connection.Set(jsonObjWithExpire, TimeSpan.FromMilliseconds(5000.5)); var ttl = (long)connection.Execute("PTTL", key); - Assert.True(ttl <= 5000.5); + Assert.True(ttl <= 5001); Assert.True(ttl >= 1000); } diff --git a/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs b/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs index 6c36fe5..3a1c1cc 100644 --- a/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs +++ b/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs @@ -457,6 +457,32 @@ public async Task TestUpdateWithTtlAsync() Assert.Equal(secondQueriedP.Id, queriedP.Id); Assert.Equal(testP.Id, secondQueriedP.Id); } + + [Fact] + public async Task TestUpdateWithTtlAsyncFractionalTimespan() + { + var collection = new RedisCollection(_connection); + var testP = new Person { Name = "Steve", Age = 32 }; + var key = await collection.InsertAsync(testP); + var queriedP = await collection.FindByIdAsync(key); + Assert.NotNull(queriedP); + queriedP.Age = 33; + TimeSpan ttl = TimeSpan.FromHours(1); + ttl = ttl.Add(TimeSpan.FromTicks(5000)); + + await collection.UpdateAsync(queriedP, ttl); + + var ttlFromKey = (double) await _connection.ExecuteAsync("PTTL", key); + + var secondQueriedP = await collection.FindByIdAsync(key); + + Assert.Equal("3600001", ttl.TotalMillisecondsString()); + Assert.InRange(ttlFromKey, ttl.TotalMilliseconds - 2000, ttl.TotalMilliseconds + 1); + Assert.NotNull(secondQueriedP); + Assert.Equal(33, secondQueriedP.Age); + Assert.Equal(secondQueriedP.Id, queriedP.Id); + Assert.Equal(testP.Id, secondQueriedP.Id); + } [Fact] public async Task TestUpdateName()