Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nx/xx #206

Merged
merged 25 commits into from
Dec 9, 2022
Merged

Add nx/xx #206

merged 25 commits into from
Dec 9, 2022

Conversation

shacharPash
Copy link
Contributor

Related to #204

/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
/// <returns>whether the operation succeeded.</returns>
public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, string json, string when, TimeSpan? timeSpan = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when here should be an enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StackExchange already has such an Enum, I was debating whether to create a new one or use theirs, so for now I left it as a string.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we can add a different enum here, I don't want to expose any data-types from SE that we don't have too.

/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
/// <returns>whether the operation succeeded.</returns>
public static bool JsonSet(this IRedisConnection connection, string key, string path, object obj, string when, TimeSpan? timeSpan = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably also want to have the equivalent overloads for the generic Set commands

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I bypassed Set and went straight from Insert to JsonSet. I think it's better that way because HSET doesn't have a When option

/// <param name="item">an item.</param>
/// <param name="timeSpan">The timespan of the document's (TTL).</param>
/// <returns>the key. or null if the key did not exist.</returns>
string? InsertIfExist(T item, TimeSpan? timeSpan = null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the explicit overloads here vs the when condition in the generic commands?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt that this way is simpler to implement without breaking too many functions, but you can also use "when".

@slorello89 slorello89 requested a review from chayim October 21, 2022 11:40
@slorello89
Copy link
Member

@chayim - at this point, I've added to much to this PR to be an objective reviewer - can you please take a look?

public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null)
{
var argList = new List<string> { timeSpan != null ? ((long)timeSpan.Value.TotalMilliseconds).ToString() : "-1", path, json };
switch (when)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use a switch instead of an if/else? If/else is better for boolean values, as traditionally it's a single block passed to the compiler.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a switch here over an if/else because the semantic isn't to see whether or not the key exists, it's whether or not the user wants to add the XX/NX options (or their equivalent), you could use an if/else-if. The perf difference between an if/else-if and switch here is going to be negligible on a tiny set(might even be 0 depending on how the IL is compiled).

public static bool JsonSet(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null)
{
var argList = new List<string> { timeSpan != null ? ((long)timeSpan.Value.TotalMilliseconds).ToString() : "-1", path, json };
switch (when)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question re if/else

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above


if (Attribute.GetCustomAttribute(type, typeof(DocumentAttribute)) is not DocumentAttribute attr || attr.StorageType == StorageType.Hash)
{
if (when == WhenKey.Always)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these be combined into a single branch? if when == && timespan.HasValue?

Branching multiple portions in the same if statement (rather than sub) is usually more performant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can, but you'd be adding an extra comparison:

if(when == WhenKey.Always && timeSpan.HasValue)
{
// set with timespan
}
else if (when == WhenKey.Always)
{
// set without timespan
}

The perf difference again would be negligible here. It's probably faster as is because in the case where when == WhenKey.Always you would always be performing two comparisons, versus with the if/else-if where you could be performing as many as 3. And then for the cases where when != WhenKey.Always in the current implementation you'd only perform 1 comparison, whereas with the if/else-if you'd be performing 3. Again, this is a super tiny thing, IMO it's better as is.

return connection.Set(obj);
}

var kvps = obj.BuildHashSet();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kvps == key value properties?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kvps in this content is referring to KeyValuePair<TKey,TValue> The response is actually a Dictionary<TKey,TValue> (equivalent of a HashMap/Dict) - but if you see it's usage in the foreach below, it's enumerator is being accessed, meaning we're effectively using it as a collection of KeyValuePair<TKey,TValue> (that's the type of enumerator a Dictionary exposes)

/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
/// <returns>whether the operation succeeded.</returns>
public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm of two sides here.

On the client side, it's very clear that we have to expose based on name XX vs NX. But here.. I think it's a bad variable name. Ideally, to help users we could just name ifExists or similar. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key here is that we are NOT in fact just exposing XX/NX, we are exposing whether we care to take any special action WRT to caring whether or not the object has existed WhenKey uses the same semantics as the When enum from StackExchange.Redis. We explicitly didn't want to expose that part of StackExchange.Redis' API, so we used a similarly named enum with the same semantics.

/// <param name="path">the path within the json to set.</param>
/// <param name="obj">the object to serialize to json.</param>
/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why timeSpan and not just seconds or evevn TTL? When I see timeSpan, as a user I assume I need seconds.. but maybe these are milliseconds?

I think the API should be more explict.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a fair bit of precedent for .NET users for using a TimeSpan over straight up using seconds or millisecond in situations like this. TimeSpan's conveniently have a fair number of functions for simply producing these, e.g. TimeSpan.From(Millisecond/Hours/Day/Weeks etc. . .). Also when performing arithmetic on two DateTime or DateTimeOffset structs, the result is a timespan. So it's just easier (and what people are used to)

/// <param name="path">the path within the json to set.</param>
/// <param name="json">the json.</param>
/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for both when + timespan

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

/// <param name="path">the path within the json to set.</param>
/// <param name="obj">the object to serialize to json.</param>
/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And again

argsList.Add(kvp.Value);
}

if (when == WhenKey.Exists)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this become a straight boolean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

chayim
chayim previously requested changes Oct 26, 2022
Copy link
Contributor

@chayim chayim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic works, some suggestions on perf and (perceived) API improvements for users.

Copy link
Member

@slorello89 slorello89 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks' @chayim - see my responses in this review.

/// <param name="path">the path within the json to set.</param>
/// <param name="obj">the object to serialize to json.</param>
/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a fair bit of precedent for .NET users for using a TimeSpan over straight up using seconds or millisecond in situations like this. TimeSpan's conveniently have a fair number of functions for simply producing these, e.g. TimeSpan.From(Millisecond/Hours/Day/Weeks etc. . .). Also when performing arithmetic on two DateTime or DateTimeOffset structs, the result is a timespan. So it's just easier (and what people are used to)

/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
/// <returns>whether the operation succeeded.</returns>
public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key here is that we are NOT in fact just exposing XX/NX, we are exposing whether we care to take any special action WRT to caring whether or not the object has existed WhenKey uses the same semantics as the When enum from StackExchange.Redis. We explicitly didn't want to expose that part of StackExchange.Redis' API, so we used a similarly named enum with the same semantics.

/// <param name="path">the path within the json to set.</param>
/// <param name="json">the json.</param>
/// <param name="when">XX - set if exist, NX - set if not exist.</param>
/// <param name="timeSpan">the the timespan to set for your (TTL).</param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

return connection.Set(obj);
}

var kvps = obj.BuildHashSet();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kvps in this content is referring to KeyValuePair<TKey,TValue> The response is actually a Dictionary<TKey,TValue> (equivalent of a HashMap/Dict) - but if you see it's usage in the foreach below, it's enumerator is being accessed, meaning we're effectively using it as a collection of KeyValuePair<TKey,TValue> (that's the type of enumerator a Dictionary exposes)

public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null)
{
var argList = new List<string> { timeSpan != null ? ((long)timeSpan.Value.TotalMilliseconds).ToString() : "-1", path, json };
switch (when)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a switch here over an if/else because the semantic isn't to see whether or not the key exists, it's whether or not the user wants to add the XX/NX options (or their equivalent), you could use an if/else-if. The perf difference between an if/else-if and switch here is going to be negligible on a tiny set(might even be 0 depending on how the IL is compiled).

public static bool JsonSet(this IRedisConnection connection, string key, string path, string json, WhenKey when, TimeSpan? timeSpan = null)
{
var argList = new List<string> { timeSpan != null ? ((long)timeSpan.Value.TotalMilliseconds).ToString() : "-1", path, json };
switch (when)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above


if (Attribute.GetCustomAttribute(type, typeof(DocumentAttribute)) is not DocumentAttribute attr || attr.StorageType == StorageType.Hash)
{
if (when == WhenKey.Always)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can, but you'd be adding an extra comparison:

if(when == WhenKey.Always && timeSpan.HasValue)
{
// set with timespan
}
else if (when == WhenKey.Always)
{
// set without timespan
}

The perf difference again would be negligible here. It's probably faster as is because in the case where when == WhenKey.Always you would always be performing two comparisons, versus with the if/else-if where you could be performing as many as 3. And then for the cases where when != WhenKey.Always in the current implementation you'd only perform 1 comparison, whereas with the if/else-if you'd be performing 3. Again, this is a super tiny thing, IMO it's better as is.

argsList.Add(kvp.Value);
}

if (when == WhenKey.Exists)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

@slorello89 slorello89 requested a review from chayim October 26, 2022 11:10
@slorello89 slorello89 linked an issue Oct 27, 2022 that may be closed by this pull request
Copy link
Member

@slorello89 slorello89 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@slorello89 slorello89 merged commit 812d633 into redis:main Dec 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JSON.SET with NX/XX
3 participants