Skip to content

Commit

Permalink
fixing issue with timestamps always being updated
Browse files Browse the repository at this point in the history
  • Loading branch information
slorello89 committed Oct 22, 2024
1 parent 1956366 commit f506618
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 2 deletions.
66 changes: 66 additions & 0 deletions src/Redis.OM/Modeling/DateTimeJsonConvertNewtonsoft.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Redis.OM.Modeling;

/// <summary>
/// Converter for Newtonsoft.
/// </summary>
internal class DateTimeJsonConvertNewtonsoft : JsonConverter
{
/// <summary>
/// Determines is the object is convertable.
/// </summary>
/// <param name="objectType">the object type.</param>
/// <returns>whether it can be converted.</returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
}

/// <summary>
/// writes the object to json.
/// </summary>
/// <param name="writer">the writer.</param>
/// <param name="value">the value.</param>
/// <param name="serializer">the serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value is DateTime dateTime)
{
// Convert DateTime to Unix timestamp
long unixTimestamp = ((DateTimeOffset)dateTime).ToUnixTimeMilliseconds();
writer.WriteValue(unixTimestamp);
}
else
{
writer.WriteNull();
}
}

/// <summary>
/// reads an object back from json.
/// </summary>
/// <param name="reader">the reader.</param>
/// <param name="objectType">the object type.</param>
/// <param name="existingValue">the existing value.</param>
/// <param name="serializer">the serializer.</param>
/// <returns>The converted object.</returns>
/// <exception cref="JsonSerializationException">thrown if issue comes up deserializing.</exception>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return objectType == typeof(DateTime?) ? null : DateTime.MinValue;
}

if (reader.TokenType == JsonToken.Integer && reader.Value is long unixTimestamp)
{
// Convert Unix timestamp back to DateTime
return DateTimeOffset.FromUnixTimeMilliseconds(unixTimestamp).UtcDateTime;
}

throw new JsonSerializationException("Invalid token type for Unix timestamp conversion.");
}
}
10 changes: 8 additions & 2 deletions src/Redis.OM/Modeling/RedisCollectionStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Redis.OM.Modeling.Vectors;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace Redis.OM.Modeling
Expand All @@ -11,6 +12,11 @@ namespace Redis.OM.Modeling
/// </summary>
public class RedisCollectionStateManager
{
private static JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore, Converters = new List<JsonConverter> { new DateTimeJsonConvertNewtonsoft() },
};

/// <summary>
/// Initializes a new instance of the <see cref="RedisCollectionStateManager"/> class.
/// </summary>
Expand Down Expand Up @@ -76,7 +82,7 @@ internal void InsertIntoSnapshot(string key, object value)

if (DocumentAttribute.StorageType == StorageType.Json)
{
var json = JToken.FromObject(value, Newtonsoft.Json.JsonSerializer.Create(new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
var json = JToken.FromObject(value, Newtonsoft.Json.JsonSerializer.Create(new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = new List<JsonConverter> { new DateTimeJsonConvertNewtonsoft() } }));
Snapshot.Add(key, json);
}
else
Expand Down Expand Up @@ -115,7 +121,7 @@ internal bool TryDetectDifferencesSingle(string key, object value, out IList<IOb
var snapshotHash = (IDictionary<string, object>)Snapshot[key];
var deletedKeys = snapshotHash.Keys.Except(dataHash.Keys).Select(x => new KeyValuePair<string, string>(x, string.Empty));
var modifiedKeys = dataHash.Where(x =>
!snapshotHash.Keys.Contains(x.Key) || snapshotHash[x.Key] != x.Value).Select(x =>
!snapshotHash.Keys.Contains(x.Key) || !snapshotHash[x.Key].Equals(x.Value)).Select(x =>
new KeyValuePair<string, string>(x.Key, x.Value.ToString()));
differences = new List<IObjectDiff>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using Redis.OM.Modeling;

namespace Redis.OM.Unit.Tests.RediSearchTests;

[Document(StorageType = StorageType.Json, Prefixes = new []{"obj"})]
public class ObjectWIthMultipleDateTimes
{
[RedisIdField]
[Indexed]
public string Id { get; set; }
public DateTime DateTime1 { get; set; }
public DateTime DateTime2 { get; set; }
}

[Document(Prefixes = new []{"obj"})]
public class ObjectWIthMultipleDateTimesHash
{
[RedisIdField]
[Indexed]
public string Id { get; set; }
public DateTime DateTime1 { get; set; }
public DateTime DateTime2 { get; set; }
}
61 changes: 61 additions & 0 deletions test/Redis.OM.Unit.Tests/RediSearchTests/SearchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,34 @@ public class SearchTests
})
};

private readonly RedisReply _mockedReplyObjectWIthMultipleDateTimes = new[]
{
new RedisReply(1),
new RedisReply(
"obj:01FVN836BNQGYMT80V7RCVY73N"),
new RedisReply(new RedisReply[]
{
"$",
"{\"Id\":\"01FVN836BNQGYMT80V7RCVY73N\",\"DateTime1\":1729592130000,\"DateTime2\":1730475900000}"
})
};

private readonly RedisReply _mockedReplyObjectWIthMultipleDateTimesHash = new[]
{
new RedisReply(1),
new RedisReply(
"obj:01FVN836BNQGYMT80V7RCVY73N"),
new RedisReply(new RedisReply[]
{
"Id",
"01FVN836BNQGYMT80V7RCVY73N",
"DateTime1",
"1729592130000",
"DateTime2",
"1730475900000"
})
};

[Fact]
public void TestBasicQuery()
{
Expand Down Expand Up @@ -1030,6 +1058,39 @@ public async Task TestUpdateJson()
await _substitute.Received().ExecuteAsync("EVALSHA", Arg.Any<string>(), "1", new RedisKey("Redis.OM.Unit.Tests.RediSearchTests.Person:01FVN836BNQGYMT80V7RCVY73N"), "SET", "$.Age", "33");
Scripts.ShaCollection.Clear();
}

[Fact]
public async Task TestUpdateJsonWithMultipleDateTimes()
{
_substitute.ExecuteAsync("FT.SEARCH", Arg.Any<object[]>()).Returns(_mockedReplyObjectWIthMultipleDateTimes);

_substitute.ExecuteAsync("EVALSHA", Arg.Any<object[]>()).Returns(Task.FromResult(new RedisReply("42")));
_substitute.ExecuteAsync("SCRIPT", Arg.Any<object[]>())
.Returns(Task.FromResult(new RedisReply("cbbf1c4fab5064f419e469cc51c563f8bf51e6fb")));
var collection = new RedisCollection<ObjectWIthMultipleDateTimes>(_substitute);
var obj = (await collection.Where(x => x.Id == "01FVN836BNQGYMT80V7RCVY73N").ToListAsync()).First();
obj.DateTime1 = obj.DateTime1.AddMilliseconds(1);
await collection.UpdateAsync(obj);
await _substitute.Received().ExecuteAsync("EVALSHA", Arg.Any<string>(), "1", new RedisKey("obj:01FVN836BNQGYMT80V7RCVY73N"), "SET", "$.DateTime1", "1729592130001");
Scripts.ShaCollection.Clear();
}


[Fact]
public async Task TestUpdateJsonWithMultipleDateTimesHash()
{
_substitute.ExecuteAsync("FT.SEARCH", Arg.Any<object[]>()).Returns(_mockedReplyObjectWIthMultipleDateTimesHash);

_substitute.ExecuteAsync("EVALSHA", Arg.Any<object[]>()).Returns(Task.FromResult(new RedisReply("42")));
_substitute.ExecuteAsync("SCRIPT", Arg.Any<object[]>())
.Returns(Task.FromResult(new RedisReply("cbbf1c4fab5064f419e469cc51c563f8bf51e6fb")));
var collection = new RedisCollection<ObjectWIthMultipleDateTimesHash>(_substitute);
var obj = (await collection.Where(x => x.Id == "01FVN836BNQGYMT80V7RCVY73N").ToListAsync()).First();
obj.DateTime1 = obj.DateTime1.AddMilliseconds(1);
await collection.UpdateAsync(obj);
await _substitute.Received().ExecuteAsync("EVALSHA", Arg.Any<string>(), "1", new RedisKey("obj:01FVN836BNQGYMT80V7RCVY73N"), "1", "DateTime1", "1729592130001");
Scripts.ShaCollection.Clear();
}

[Fact]
public async Task TestUpdateJsonUnloadedScriptAsync()
Expand Down

0 comments on commit f506618

Please sign in to comment.