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 Condition.SortedSetLengthEqual with min and max score #1332

Merged
merged 2 commits into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions src/StackExchange.Redis/Condition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,20 +231,47 @@ public static Condition StringNotEqual(RedisKey key, RedisValue value)
/// <param name="length">The length the sorted set must be equal to.</param>
public static Condition SortedSetLengthEqual(RedisKey key, long length) => new LengthCondition(key, RedisType.SortedSet, 0, length);

/// <summary>
/// Enforces that the given sorted set contains a certain number of members with scores in the given range
/// </summary>
/// <param name="key">The key of the sorted set to check.</param>
/// <param name="length">The length the sorted set must be equal to.</param>
/// <param name="min">Minimum inclusive score.</param>
/// <param name="max">Maximum inclusive score.</param>
public static Condition SortedSetLengthEqual(RedisKey key, long length, double min = double.NegativeInfinity, double max = double.PositiveInfinity) => new SortedSetRangeLengthCondition(key, min, max, 0, length);

/// <summary>
/// Enforces that the given sorted set cardinality is less than a certain value
/// </summary>
/// <param name="key">The key of the sorted set to check.</param>
/// <param name="length">The length the sorted set must be less than.</param>
public static Condition SortedSetLengthLessThan(RedisKey key, long length) => new LengthCondition(key, RedisType.SortedSet, 1, length);

/// <summary>
/// Enforces that the given sorted set contains less than a certain number of members with scores in the given range
/// </summary>
/// <param name="key">The key of the sorted set to check.</param>
/// <param name="length">The length the sorted set must be equal to.</param>
/// <param name="min">Minimum inclusive score.</param>
/// <param name="max">Maximum inclusive score.</param>
public static Condition SortedSetLengthLessThan(RedisKey key, long length, double min = double.NegativeInfinity, double max = double.PositiveInfinity) => new SortedSetRangeLengthCondition(key, min, max, 1, length);

/// <summary>
/// Enforces that the given sorted set cardinality is greater than a certain value
/// </summary>
/// <param name="key">The key of the sorted set to check.</param>
/// <param name="length">The length the sorted set must be greater than.</param>
public static Condition SortedSetLengthGreaterThan(RedisKey key, long length) => new LengthCondition(key, RedisType.SortedSet, -1, length);

/// <summary>
/// Enforces that the given sorted set contains more than a certain number of members with scores in the given range
/// </summary>
/// <param name="key">The key of the sorted set to check.</param>
/// <param name="length">The length the sorted set must be equal to.</param>
/// <param name="min">Minimum inclusive score.</param>
/// <param name="max">Maximum inclusive score.</param>
public static Condition SortedSetLengthGreaterThan(RedisKey key, long length, double min = double.NegativeInfinity, double max = double.PositiveInfinity) => new SortedSetRangeLengthCondition(key, min, max, -1, length);

/// <summary>
/// Enforces that the given sorted set contains a certain member
/// </summary>
Expand Down Expand Up @@ -732,6 +759,76 @@ internal override bool TryValidate(in RawResult result, out bool value)
}
}

internal class SortedSetRangeLengthCondition : Condition
{
internal override Condition MapKeys(Func<RedisKey, RedisKey> map)
{
return new SortedSetRangeLengthCondition(map(key), min, max, compareToResult, expectedLength);
}

private readonly RedisValue min;
private readonly RedisValue max;
private readonly int compareToResult;
private readonly long expectedLength;
private readonly RedisKey key;

public SortedSetRangeLengthCondition(in RedisKey key, RedisValue min, RedisValue max, int compareToResult, long expectedLength)
{
if (key.IsNull) throw new ArgumentException(nameof(key));
this.key = key;
this.min = min;
this.max = max;
this.compareToResult = compareToResult;
this.expectedLength = expectedLength;
}

public override string ToString()
{
return ((string)key) + " " + RedisType.SortedSet + " range[" + min + ", " + max + "] length" + GetComparisonString() + expectedLength;
}

private string GetComparisonString()
{
return compareToResult == 0 ? " == " : (compareToResult < 0 ? " > " : " < ");
}

internal override void CheckCommands(CommandMap commandMap)
{
commandMap.AssertAvailable(RedisCommand.ZCOUNT);
}

internal sealed override IEnumerable<Message> CreateMessages(int db, IResultBox resultBox)
{
yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key);

var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.ZCOUNT, key, min, max);
message.SetSource(ConditionProcessor.Default, resultBox);
yield return message;
}

internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}

internal override bool TryValidate(in RawResult result, out bool value)
{
switch (result.Type)
{
case ResultType.BulkString:
case ResultType.SimpleString:
case ResultType.Integer:
var parsed = result.AsRedisValue();
value = parsed.IsInteger && (expectedLength.CompareTo((long)parsed) == compareToResult);
ConnectionMultiplexer.TraceWithoutContext("actual: " + (string)parsed + "; expected: " + expectedLength +
"; wanted: " + GetComparisonString() + "; voting: " + value);
return true;
}
value = false;
return false;
}
}

internal class SortedSetScoreCondition : Condition
{
internal override Condition MapKeys(Func<RedisKey, RedisKey> map)
Expand Down
78 changes: 78 additions & 0 deletions tests/StackExchange.Redis.Tests/Transactions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,84 @@ public async Task BasicTranWithSortedSetCardinalityCondition(string value, Compa
}
}

[Theory]
[InlineData(1, 4, ComparisonType.Equal, 5L, false)]
[InlineData(1, 4, ComparisonType.Equal, 4L, true)]
[InlineData(1, 2, ComparisonType.Equal, 3L, false)]
[InlineData(1, 1, ComparisonType.Equal, 2L, false)]
[InlineData(0, 0, ComparisonType.Equal, 0L, false)]

[InlineData(1, 4, ComparisonType.LessThan, 5L, true)]
[InlineData(1, 4, ComparisonType.LessThan, 4L, false)]
[InlineData(1, 3, ComparisonType.LessThan, 3L, false)]
[InlineData(1, 1, ComparisonType.LessThan, 2L, true)]
[InlineData(0, 0, ComparisonType.LessThan, 0L, false)]

[InlineData(1, 5, ComparisonType.GreaterThan, 5L, false)]
[InlineData(1, 4, ComparisonType.GreaterThan, 4L, false)]
[InlineData(1, 4, ComparisonType.GreaterThan, 3L, true)]
[InlineData(1, 2, ComparisonType.GreaterThan, 2L, false)]
[InlineData(0, 0, ComparisonType.GreaterThan, 0L, true)]
public async Task BasicTranWithSortedSetRangeCountCondition(double min, double max, ComparisonType type, long length, bool expectTranResult)
{
using (var muxer = Create())
{
RedisKey key = Me(), key2 = Me() + "2";
var db = muxer.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.KeyDelete(key2, CommandFlags.FireAndForget);

var expectSuccess = false;
Condition condition = null;
var valueLength = (int)(max - min) + 1;
switch (type)
{
case ComparisonType.Equal:
expectSuccess = valueLength == length;
condition = Condition.SortedSetLengthEqual(key2, length, min, max);
break;
case ComparisonType.GreaterThan:
expectSuccess = valueLength > length;
condition = Condition.SortedSetLengthGreaterThan(key2, length, min, max);
break;
case ComparisonType.LessThan:
expectSuccess = valueLength < length;
condition = Condition.SortedSetLengthLessThan(key2, length, min, max);
break;
}

for (var i = 0; i < 5; i++)
{
db.SortedSetAdd(key2, i, i, flags: CommandFlags.FireAndForget);
}
Assert.False(db.KeyExists(key));
Assert.Equal(5, db.SortedSetLength(key2));

var tran = db.CreateTransaction();
var cond = tran.AddCondition(condition);
var push = tran.StringSetAsync(key, "any value");
var exec = tran.ExecuteAsync();
var get = db.StringLength(key);

Assert.Equal(expectTranResult, await exec);

if (expectSuccess)
{
Assert.True(await exec, "eq: exec");
Assert.True(cond.WasSatisfied, "eq: was satisfied");
Assert.True(await push); // eq: push
Assert.Equal("any value".Length, get); // eq: get
}
else
{
Assert.False(await exec, "neq: exec");
Assert.False(cond.WasSatisfied, "neq: was satisfied");
Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push
Assert.Equal(0, get); // neq: get
}
}
}

[Theory]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
Expand Down