Commit 9f326234 authored by Daniel Chandler's avatar Daniel Chandler Committed by Nick Craver

Add Transaction Conditions for Set Contains and Sorted Set Contains (#778)

parent 2bc189cc
......@@ -544,6 +544,48 @@ public void BasicTranWithSetCardinalityCondition(string value, ComparisonType ty
}
}
[Theory]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(true, false, false)]
[InlineData(true, true, true)]
public void BasicTranWithSetContainsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult)
{
using (var muxer = Create(disabledCommands: new[] { "info", "config" }))
{
RedisKey key = Me(), key2 = Me() + "2";
var db = muxer.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.KeyDelete(key2, CommandFlags.FireAndForget);
RedisValue member = "value";
if (keyExists) db.SetAdd(key2, member, flags: CommandFlags.FireAndForget);
Assert.False(db.KeyExists(key));
Assert.Equal(keyExists, db.SetContains(key2, member));
var tran = db.CreateTransaction();
var cond = tran.AddCondition(demandKeyExists ? Condition.SetContains(key2, member) : Condition.SetNotContains(key2, member));
var incr = tran.StringIncrementAsync(key);
var exec = tran.ExecuteAsync();
var get = db.StringGet(key);
Assert.Equal(expectTranResult, db.Wait(exec));
if (demandKeyExists == keyExists)
{
Assert.True(db.Wait(exec), "eq: exec");
Assert.True(cond.WasSatisfied, "eq: was satisfied");
Assert.Equal(1, db.Wait(incr)); // eq: incr
Assert.Equal(1, (long)get); // eq: get
}
else
{
Assert.False(db.Wait(exec), "neq: exec");
Assert.False(cond.WasSatisfied, "neq: was satisfied");
Assert.Equal(TaskStatus.Canceled, incr.Status); // neq: incr
Assert.Equal(0, (long)get); // neq: get
}
}
}
[Theory]
[InlineData("five", ComparisonType.Equal, 5L, false)]
[InlineData("four", ComparisonType.Equal, 4L, true)]
......@@ -622,6 +664,48 @@ public void BasicTranWithSortedSetCardinalityCondition(string value, ComparisonT
}
}
[Theory]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(true, false, false)]
[InlineData(true, true, true)]
public void BasicTranWithSortedSetContainsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult)
{
using (var muxer = Create(disabledCommands: new[] { "info", "config" }))
{
RedisKey key = Me(), key2 = Me() + "2";
var db = muxer.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.KeyDelete(key2, CommandFlags.FireAndForget);
RedisValue member = "value";
if (keyExists) db.SortedSetAdd(key2, member, 0.0, flags: CommandFlags.FireAndForget);
Assert.False(db.KeyExists(key));
Assert.Equal(keyExists, db.SortedSetScore(key2, member).HasValue);
var tran = db.CreateTransaction();
var cond = tran.AddCondition(demandKeyExists ? Condition.SortedSetContains(key2, member) : Condition.SortedSetNotContains(key2, member));
var incr = tran.StringIncrementAsync(key);
var exec = tran.ExecuteAsync();
var get = db.StringGet(key);
Assert.Equal(expectTranResult, db.Wait(exec));
if (demandKeyExists == keyExists)
{
Assert.True(db.Wait(exec), "eq: exec");
Assert.True(cond.WasSatisfied, "eq: was satisfied");
Assert.Equal(1, db.Wait(incr)); // eq: incr
Assert.Equal(1, (long)get); // eq: get
}
else
{
Assert.False(db.Wait(exec), "neq: exec");
Assert.False(cond.WasSatisfied, "neq: was satisfied");
Assert.Equal(TaskStatus.Canceled, incr.Status); // neq: incr
Assert.Equal(0, (long)get); // neq: get
}
}
}
[Theory]
[InlineData("five", ComparisonType.Equal, 5L, false)]
[InlineData("four", ComparisonType.Equal, 4L, true)]
......
......@@ -29,7 +29,7 @@ public static Condition HashEqual(RedisKey key, RedisValue hashField, RedisValue
public static Condition HashExists(RedisKey key, RedisValue hashField)
{
if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField));
return new ExistsCondition(key, hashField, true);
return new ExistsCondition(key, RedisType.Hash, hashField, true);
}
/// <summary>
......@@ -48,7 +48,7 @@ public static Condition HashNotEqual(RedisKey key, RedisValue hashField, RedisVa
public static Condition HashNotExists(RedisKey key, RedisValue hashField)
{
if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField));
return new ExistsCondition(key, hashField, false);
return new ExistsCondition(key, RedisType.Hash, hashField, false);
}
/// <summary>
......@@ -56,7 +56,7 @@ public static Condition HashNotExists(RedisKey key, RedisValue hashField)
/// </summary>
public static Condition KeyExists(RedisKey key)
{
return new ExistsCondition(key, RedisValue.Null, true);
return new ExistsCondition(key, RedisType.None, RedisValue.Null, true);
}
/// <summary>
......@@ -64,7 +64,7 @@ public static Condition KeyExists(RedisKey key)
/// </summary>
public static Condition KeyNotExists(RedisKey key)
{
return new ExistsCondition(key, RedisValue.Null, false);
return new ExistsCondition(key, RedisType.None, RedisValue.Null, false);
}
/// <summary>
......@@ -213,6 +213,22 @@ public static Condition SetLengthGreaterThan(RedisKey key, long length)
return new LengthCondition(key, RedisType.Set, -1, length);
}
/// <summary>
/// Enforces that the given set contains a certain member
/// </summary>
public static Condition SetContains(RedisKey key, RedisValue member)
{
return new ExistsCondition(key, RedisType.Set, member, true);
}
/// <summary>
/// Enforces that the given set does not contain a certain member
/// </summary>
public static Condition SetNotContains(RedisKey key, RedisValue member)
{
return new ExistsCondition(key, RedisType.Set, member, false);
}
/// <summary>
/// Enforces that the given sorted set cardinality is a certain value
/// </summary>
......@@ -237,6 +253,22 @@ public static Condition SortedSetLengthGreaterThan(RedisKey key, long length)
return new LengthCondition(key, RedisType.SortedSet, -1, length);
}
/// <summary>
/// Enforces that the given sorted set contains a certain member
/// </summary>
public static Condition SortedSetContains(RedisKey key, RedisValue member)
{
return new ExistsCondition(key, RedisType.SortedSet, member, true);
}
/// <summary>
/// Enforces that the given sorted set does not contain a certain member
/// </summary>
public static Condition SortedSetNotContains(RedisKey key, RedisValue member)
{
return new ExistsCondition(key, RedisType.SortedSet, member, false);
}
internal abstract void CheckCommands(CommandMap commandMap);
internal abstract IEnumerable<Message> CreateMessages(int db, ResultBox resultBox);
......@@ -298,38 +330,62 @@ internal override void WriteImpl(PhysicalConnection physical)
internal class ExistsCondition : Condition
{
private readonly bool expectedResult;
private readonly RedisValue hashField;
private readonly RedisValue expectedValue;
private readonly RedisKey key;
private readonly RedisType type;
private readonly RedisCommand cmd;
internal override Condition MapKeys(Func<RedisKey,RedisKey> map)
{
return new ExistsCondition(map(key), hashField, expectedResult);
return new ExistsCondition(map(key), type, expectedValue, expectedResult);
}
public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
public ExistsCondition(RedisKey key, RedisType type, RedisValue expectedValue, bool expectedResult)
{
if (key.IsNull) throw new ArgumentException("key");
this.key = key;
this.hashField = hashField;
this.type = type;
this.expectedValue = expectedValue;
this.expectedResult = expectedResult;
if (expectedValue.IsNull) {
cmd = RedisCommand.EXISTS;
}
else {
switch (type) {
case RedisType.Hash:
cmd = RedisCommand.HEXISTS;
break;
case RedisType.Set:
cmd = RedisCommand.SISMEMBER;
break;
case RedisType.SortedSet:
cmd = RedisCommand.ZSCORE;
break;
default:
throw new ArgumentException(nameof(type));
}
}
}
public override string ToString()
{
return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
return (expectedValue.IsNull ? key.ToString() : ((string)key) + " " + type + " > " + expectedValue)
+ (expectedResult ? " exists" : " does not exists");
}
internal override void CheckCommands(CommandMap commandMap)
{
commandMap.AssertAvailable(hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS);
commandMap.AssertAvailable(cmd);
}
internal override IEnumerable<Message> CreateMessages(int db, ResultBox resultBox)
{
yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key);
var cmd = hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS;
var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, hashField);
var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, expectedValue);
message.SetSource(ConditionProcessor.Default, resultBox);
yield return message;
}
......@@ -340,6 +396,14 @@ internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrateg
}
internal override bool TryValidate(RawResult result, out bool value)
{
switch (type) {
case RedisType.SortedSet:
var parsedValue = result.AsRedisValue();
value = (parsedValue.IsNull != expectedResult);
ConnectionMultiplexer.TraceWithoutContext("exists: " + parsedValue + "; expected: " + expectedResult + "; voting: " + value);
return true;
default:
bool parsed;
if (ResultProcessor.DemandZeroOrOneProcessor.TryGet(result, out parsed))
{
......@@ -351,6 +415,7 @@ internal override bool TryValidate(RawResult result, out bool value)
return false;
}
}
}
internal class EqualsCondition : Condition
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment