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 ...@@ -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] [Theory]
[InlineData("five", ComparisonType.Equal, 5L, false)] [InlineData("five", ComparisonType.Equal, 5L, false)]
[InlineData("four", ComparisonType.Equal, 4L, true)] [InlineData("four", ComparisonType.Equal, 4L, true)]
...@@ -622,6 +664,48 @@ public void BasicTranWithSortedSetCardinalityCondition(string value, ComparisonT ...@@ -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] [Theory]
[InlineData("five", ComparisonType.Equal, 5L, false)] [InlineData("five", ComparisonType.Equal, 5L, false)]
[InlineData("four", ComparisonType.Equal, 4L, true)] [InlineData("four", ComparisonType.Equal, 4L, true)]
......
...@@ -29,7 +29,7 @@ public static Condition HashEqual(RedisKey key, RedisValue hashField, RedisValue ...@@ -29,7 +29,7 @@ public static Condition HashEqual(RedisKey key, RedisValue hashField, RedisValue
public static Condition HashExists(RedisKey key, RedisValue hashField) public static Condition HashExists(RedisKey key, RedisValue hashField)
{ {
if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField));
return new ExistsCondition(key, hashField, true); return new ExistsCondition(key, RedisType.Hash, hashField, true);
} }
/// <summary> /// <summary>
...@@ -48,7 +48,7 @@ public static Condition HashNotEqual(RedisKey key, RedisValue hashField, RedisVa ...@@ -48,7 +48,7 @@ public static Condition HashNotEqual(RedisKey key, RedisValue hashField, RedisVa
public static Condition HashNotExists(RedisKey key, RedisValue hashField) public static Condition HashNotExists(RedisKey key, RedisValue hashField)
{ {
if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField));
return new ExistsCondition(key, hashField, false); return new ExistsCondition(key, RedisType.Hash, hashField, false);
} }
/// <summary> /// <summary>
...@@ -56,7 +56,7 @@ public static Condition HashNotExists(RedisKey key, RedisValue hashField) ...@@ -56,7 +56,7 @@ public static Condition HashNotExists(RedisKey key, RedisValue hashField)
/// </summary> /// </summary>
public static Condition KeyExists(RedisKey key) public static Condition KeyExists(RedisKey key)
{ {
return new ExistsCondition(key, RedisValue.Null, true); return new ExistsCondition(key, RedisType.None, RedisValue.Null, true);
} }
/// <summary> /// <summary>
...@@ -64,7 +64,7 @@ public static Condition KeyExists(RedisKey key) ...@@ -64,7 +64,7 @@ public static Condition KeyExists(RedisKey key)
/// </summary> /// </summary>
public static Condition KeyNotExists(RedisKey key) public static Condition KeyNotExists(RedisKey key)
{ {
return new ExistsCondition(key, RedisValue.Null, false); return new ExistsCondition(key, RedisType.None, RedisValue.Null, false);
} }
/// <summary> /// <summary>
...@@ -213,6 +213,22 @@ public static Condition SetLengthGreaterThan(RedisKey key, long length) ...@@ -213,6 +213,22 @@ public static Condition SetLengthGreaterThan(RedisKey key, long length)
return new LengthCondition(key, RedisType.Set, -1, 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> /// <summary>
/// Enforces that the given sorted set cardinality is a certain value /// Enforces that the given sorted set cardinality is a certain value
/// </summary> /// </summary>
...@@ -237,6 +253,22 @@ public static Condition SortedSetLengthGreaterThan(RedisKey key, long length) ...@@ -237,6 +253,22 @@ public static Condition SortedSetLengthGreaterThan(RedisKey key, long length)
return new LengthCondition(key, RedisType.SortedSet, -1, 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 void CheckCommands(CommandMap commandMap);
internal abstract IEnumerable<Message> CreateMessages(int db, ResultBox resultBox); internal abstract IEnumerable<Message> CreateMessages(int db, ResultBox resultBox);
...@@ -298,38 +330,62 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -298,38 +330,62 @@ internal override void WriteImpl(PhysicalConnection physical)
internal class ExistsCondition : Condition internal class ExistsCondition : Condition
{ {
private readonly bool expectedResult; private readonly bool expectedResult;
private readonly RedisValue hashField; private readonly RedisValue expectedValue;
private readonly RedisKey key; private readonly RedisKey key;
private readonly RedisType type;
private readonly RedisCommand cmd;
internal override Condition MapKeys(Func<RedisKey,RedisKey> map) 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"); if (key.IsNull) throw new ArgumentException("key");
this.key = key; this.key = key;
this.hashField = hashField; this.type = type;
this.expectedValue = expectedValue;
this.expectedResult = expectedResult; 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() 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"); + (expectedResult ? " exists" : " does not exists");
} }
internal override void CheckCommands(CommandMap commandMap) 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) internal override IEnumerable<Message> CreateMessages(int db, ResultBox resultBox)
{ {
yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); 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, expectedValue);
var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, hashField);
message.SetSource(ConditionProcessor.Default, resultBox); message.SetSource(ConditionProcessor.Default, resultBox);
yield return message; yield return message;
} }
...@@ -340,15 +396,24 @@ internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrateg ...@@ -340,15 +396,24 @@ internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrateg
} }
internal override bool TryValidate(RawResult result, out bool value) internal override bool TryValidate(RawResult result, out bool value)
{ {
bool parsed; switch (type) {
if (ResultProcessor.DemandZeroOrOneProcessor.TryGet(result, out parsed)) case RedisType.SortedSet:
{ var parsedValue = result.AsRedisValue();
value = parsed == expectedResult; value = (parsedValue.IsNull != expectedResult);
ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value); ConnectionMultiplexer.TraceWithoutContext("exists: " + parsedValue + "; expected: " + expectedResult + "; voting: " + value);
return true; return true;
default:
bool parsed;
if (ResultProcessor.DemandZeroOrOneProcessor.TryGet(result, out parsed))
{
value = parsed == expectedResult;
ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value);
return true;
}
value = false;
return false;
} }
value = false;
return false;
} }
} }
......
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