Skip to content

Commit

Permalink
Add Condition.SortedSetLengthEqual with min and max score (#1332)
Browse files Browse the repository at this point in the history
* Add sorte set length condition with range

* Improve range condition test
  • Loading branch information
Min authored Feb 3, 2020
1 parent f5d49c2 commit aec5fc3
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
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

0 comments on commit aec5fc3

Please sign in to comment.