Commit 3d817bf4 authored by Marc Gravell's avatar Marc Gravell

Merge pull request #302 from AlphaGremlin/master

Add Transaction Conditions for List Index
parents de41b0e6 ead86045
...@@ -207,6 +207,96 @@ public void BasicTranWithHashEqualsCondition(string expected, string value, bool ...@@ -207,6 +207,96 @@ public void BasicTranWithHashEqualsCondition(string expected, string value, bool
} }
} }
[Test]
[TestCase(false, false, true)]
[TestCase(false, true, false)]
[TestCase(true, false, false)]
[TestCase(true, true, true)]
public void BasicTranWithListExistsCondition(bool demandKeyExists, bool keyExists, bool expectTran)
{
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);
if (keyExists) db.ListRightPush(key2, "any value", flags: CommandFlags.FireAndForget);
Assert.IsFalse(db.KeyExists(key));
Assert.AreEqual(keyExists, db.KeyExists(key2));
var tran = db.CreateTransaction();
var cond = tran.AddCondition(demandKeyExists ? Condition.ListIndexExists(key2, 0) : Condition.ListIndexNotExists(key2, 0));
var push = tran.ListRightPushAsync(key, "any value");
var exec = tran.ExecuteAsync();
var get = db.ListGetByIndex(key, 0);
Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result");
if (demandKeyExists == keyExists)
{
Assert.IsTrue(db.Wait(exec), "eq: exec");
Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied");
Assert.AreEqual(1, db.Wait(push), "eq: push");
Assert.AreEqual("any value", (string)get, "eq: get");
}
else
{
Assert.IsFalse(db.Wait(exec), "neq: exec");
Assert.False(cond.WasSatisfied, "neq: was satisfied");
Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push");
Assert.AreEqual(null, (string)get, "neq: get");
}
}
}
[Test]
[TestCase("same", "same", true, true)]
[TestCase("x", "y", true, false)]
[TestCase("x", null, true, false)]
[TestCase(null, "y", true, false)]
[TestCase(null, null, true, true)]
[TestCase("same", "same", false, false)]
[TestCase("x", "y", false, true)]
[TestCase("x", null, false, true)]
[TestCase(null, "y", false, true)]
[TestCase(null, null, false, false)]
public void BasicTranWithListEqualsCondition(string expected, string value, bool expectEqual, bool expectTran)
{
using (var muxer = Create())
{
RedisKey key = Me(), key2 = Me() + "2";
var db = muxer.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.KeyDelete(key2, CommandFlags.FireAndForget);
if (value != null) db.ListRightPush(key2, value, flags: CommandFlags.FireAndForget);
Assert.IsFalse(db.KeyExists(key));
Assert.AreEqual(value, (string)db.ListGetByIndex(key2, 0));
var tran = db.CreateTransaction();
var cond = tran.AddCondition(expectEqual ? Condition.ListIndexEqual(key2, 0, expected) : Condition.ListIndexNotEqual(key2, 0, expected));
var push = tran.ListRightPushAsync(key, "any value");
var exec = tran.ExecuteAsync();
var get = db.ListGetByIndex(key, 0);
Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result");
if (expectEqual == (value == expected))
{
Assert.IsTrue(db.Wait(exec), "eq: exec");
Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied");
Assert.AreEqual(1, db.Wait(push), "eq: push");
Assert.AreEqual("any value", (string)get, "eq: get");
}
else
{
Assert.IsFalse(db.Wait(exec), "neq: exec");
Assert.False(cond.WasSatisfied, "neq: was satisfied");
Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push");
Assert.AreEqual(null, (string)get, "neq: get");
}
}
}
[Test] [Test]
public async void BasicTran() public async void BasicTran()
{ {
......
...@@ -67,6 +67,38 @@ public static Condition KeyNotExists(RedisKey key) ...@@ -67,6 +67,38 @@ public static Condition KeyNotExists(RedisKey key)
return new ExistsCondition(key, RedisValue.Null, false); return new ExistsCondition(key, RedisValue.Null, false);
} }
/// <summary>
/// Enforces that the given list index must have the specified value
/// </summary>
public static Condition ListIndexEqual(RedisKey key, long index, RedisValue value)
{
return new ListCondition(key, index, true, value);
}
/// <summary>
/// Enforces that the given list index must exist
/// </summary>
public static Condition ListIndexExists(RedisKey key, long index)
{
return new ListCondition(key, index, true, null);
}
/// <summary>
/// Enforces that the given list index must not have the specified value
/// </summary>
public static Condition ListIndexNotEqual(RedisKey key, long index, RedisValue value)
{
return new ListCondition(key, index, false, value);
}
/// <summary>
/// Enforces that the given list index must not exist
/// </summary>
public static Condition ListIndexNotExists(RedisKey key, long index)
{
return new ListCondition(key, index, false, null);
}
/// <summary> /// <summary>
/// Enforces that the given key must have the specified value /// Enforces that the given key must have the specified value
/// </summary> /// </summary>
...@@ -262,6 +294,76 @@ internal override bool TryValidate(RawResult result, out bool value) ...@@ -262,6 +294,76 @@ internal override bool TryValidate(RawResult result, out bool value)
return false; return false;
} }
} }
internal class ListCondition : Condition
{
internal override Condition MapKeys(Func<RedisKey,RedisKey> map)
{
return new ListCondition(map(key), index, expectedResult, expectedValue);
}
private readonly bool expectedResult;
private readonly long index;
private readonly RedisValue? expectedValue;
private readonly RedisKey key;
public ListCondition(RedisKey key, long index, bool expectedResult, RedisValue? expectedValue)
{
if (key.IsNull) throw new ArgumentException("key");
this.key = key;
this.index = index;
this.expectedResult = expectedResult;
this.expectedValue = expectedValue;
}
public override string ToString()
{
return ((string)key) + "[" + index.ToString() + "]"
+ (expectedValue.HasValue ? (expectedResult ? " == " : " != ") + expectedValue.Value : (expectedResult ? " exists" : " does not exist"));
}
internal override void CheckCommands(CommandMap commandMap)
{
commandMap.AssertAvailable(RedisCommand.LINDEX);
}
internal sealed override IEnumerable<Message> CreateMessages(int db, ResultBox resultBox)
{
yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key);
var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.LINDEX, key, index);
message.SetSource(ConditionProcessor.Default, resultBox);
yield return message;
}
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
internal override bool TryValidate(RawResult result, out bool value)
{
switch (result.Type)
{
case ResultType.BulkString:
case ResultType.SimpleString:
case ResultType.Integer:
var parsed = result.AsRedisValue();
if (expectedValue.HasValue)
{
value = (parsed == expectedValue.Value) == expectedResult;
ConnectionMultiplexer.TraceWithoutContext("actual: " + (string)parsed + "; expected: " + (string)expectedValue.Value +
"; wanted: " + (expectedResult ? "==" : "!=") + "; voting: " + value);
}
else
{
value = (parsed.IsNull != expectedResult);
ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value);
}
return true;
}
value = false;
return false;
}
}
} }
/// <summary> /// <summary>
......
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