Skip to content

Commit 9f32623

Browse files
AlphaGremlinNickCraver
authored andcommitted
Add Transaction Conditions for Set Contains and Sorted Set Contains (#778)
1 parent 2bc189c commit 9f32623

File tree

2 files changed

+169
-20
lines changed

2 files changed

+169
-20
lines changed

StackExchange.Redis.Tests/Transactions.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,48 @@ public void BasicTranWithSetCardinalityCondition(string value, ComparisonType ty
544544
}
545545
}
546546

547+
[Theory]
548+
[InlineData(false, false, true)]
549+
[InlineData(false, true, false)]
550+
[InlineData(true, false, false)]
551+
[InlineData(true, true, true)]
552+
public void BasicTranWithSetContainsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult)
553+
{
554+
using (var muxer = Create(disabledCommands: new[] { "info", "config" }))
555+
{
556+
RedisKey key = Me(), key2 = Me() + "2";
557+
var db = muxer.GetDatabase();
558+
db.KeyDelete(key, CommandFlags.FireAndForget);
559+
db.KeyDelete(key2, CommandFlags.FireAndForget);
560+
RedisValue member = "value";
561+
if (keyExists) db.SetAdd(key2, member, flags: CommandFlags.FireAndForget);
562+
Assert.False(db.KeyExists(key));
563+
Assert.Equal(keyExists, db.SetContains(key2, member));
564+
565+
var tran = db.CreateTransaction();
566+
var cond = tran.AddCondition(demandKeyExists ? Condition.SetContains(key2, member) : Condition.SetNotContains(key2, member));
567+
var incr = tran.StringIncrementAsync(key);
568+
var exec = tran.ExecuteAsync();
569+
var get = db.StringGet(key);
570+
571+
Assert.Equal(expectTranResult, db.Wait(exec));
572+
if (demandKeyExists == keyExists)
573+
{
574+
Assert.True(db.Wait(exec), "eq: exec");
575+
Assert.True(cond.WasSatisfied, "eq: was satisfied");
576+
Assert.Equal(1, db.Wait(incr)); // eq: incr
577+
Assert.Equal(1, (long)get); // eq: get
578+
}
579+
else
580+
{
581+
Assert.False(db.Wait(exec), "neq: exec");
582+
Assert.False(cond.WasSatisfied, "neq: was satisfied");
583+
Assert.Equal(TaskStatus.Canceled, incr.Status); // neq: incr
584+
Assert.Equal(0, (long)get); // neq: get
585+
}
586+
}
587+
}
588+
547589
[Theory]
548590
[InlineData("five", ComparisonType.Equal, 5L, false)]
549591
[InlineData("four", ComparisonType.Equal, 4L, true)]
@@ -622,6 +664,48 @@ public void BasicTranWithSortedSetCardinalityCondition(string value, ComparisonT
622664
}
623665
}
624666

667+
[Theory]
668+
[InlineData(false, false, true)]
669+
[InlineData(false, true, false)]
670+
[InlineData(true, false, false)]
671+
[InlineData(true, true, true)]
672+
public void BasicTranWithSortedSetContainsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult)
673+
{
674+
using (var muxer = Create(disabledCommands: new[] { "info", "config" }))
675+
{
676+
RedisKey key = Me(), key2 = Me() + "2";
677+
var db = muxer.GetDatabase();
678+
db.KeyDelete(key, CommandFlags.FireAndForget);
679+
db.KeyDelete(key2, CommandFlags.FireAndForget);
680+
RedisValue member = "value";
681+
if (keyExists) db.SortedSetAdd(key2, member, 0.0, flags: CommandFlags.FireAndForget);
682+
Assert.False(db.KeyExists(key));
683+
Assert.Equal(keyExists, db.SortedSetScore(key2, member).HasValue);
684+
685+
var tran = db.CreateTransaction();
686+
var cond = tran.AddCondition(demandKeyExists ? Condition.SortedSetContains(key2, member) : Condition.SortedSetNotContains(key2, member));
687+
var incr = tran.StringIncrementAsync(key);
688+
var exec = tran.ExecuteAsync();
689+
var get = db.StringGet(key);
690+
691+
Assert.Equal(expectTranResult, db.Wait(exec));
692+
if (demandKeyExists == keyExists)
693+
{
694+
Assert.True(db.Wait(exec), "eq: exec");
695+
Assert.True(cond.WasSatisfied, "eq: was satisfied");
696+
Assert.Equal(1, db.Wait(incr)); // eq: incr
697+
Assert.Equal(1, (long)get); // eq: get
698+
}
699+
else
700+
{
701+
Assert.False(db.Wait(exec), "neq: exec");
702+
Assert.False(cond.WasSatisfied, "neq: was satisfied");
703+
Assert.Equal(TaskStatus.Canceled, incr.Status); // neq: incr
704+
Assert.Equal(0, (long)get); // neq: get
705+
}
706+
}
707+
}
708+
625709
[Theory]
626710
[InlineData("five", ComparisonType.Equal, 5L, false)]
627711
[InlineData("four", ComparisonType.Equal, 4L, true)]

StackExchange.Redis/StackExchange/Redis/Condition.cs

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static Condition HashEqual(RedisKey key, RedisValue hashField, RedisValue
2929
public static Condition HashExists(RedisKey key, RedisValue hashField)
3030
{
3131
if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField));
32-
return new ExistsCondition(key, hashField, true);
32+
return new ExistsCondition(key, RedisType.Hash, hashField, true);
3333
}
3434

3535
/// <summary>
@@ -48,23 +48,23 @@ public static Condition HashNotEqual(RedisKey key, RedisValue hashField, RedisVa
4848
public static Condition HashNotExists(RedisKey key, RedisValue hashField)
4949
{
5050
if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField));
51-
return new ExistsCondition(key, hashField, false);
51+
return new ExistsCondition(key, RedisType.Hash, hashField, false);
5252
}
5353

5454
/// <summary>
5555
/// Enforces that the given key must exist
5656
/// </summary>
5757
public static Condition KeyExists(RedisKey key)
5858
{
59-
return new ExistsCondition(key, RedisValue.Null, true);
59+
return new ExistsCondition(key, RedisType.None, RedisValue.Null, true);
6060
}
6161

6262
/// <summary>
6363
/// Enforces that the given key must not exist
6464
/// </summary>
6565
public static Condition KeyNotExists(RedisKey key)
6666
{
67-
return new ExistsCondition(key, RedisValue.Null, false);
67+
return new ExistsCondition(key, RedisType.None, RedisValue.Null, false);
6868
}
6969

7070
/// <summary>
@@ -213,6 +213,22 @@ public static Condition SetLengthGreaterThan(RedisKey key, long length)
213213
return new LengthCondition(key, RedisType.Set, -1, length);
214214
}
215215

216+
/// <summary>
217+
/// Enforces that the given set contains a certain member
218+
/// </summary>
219+
public static Condition SetContains(RedisKey key, RedisValue member)
220+
{
221+
return new ExistsCondition(key, RedisType.Set, member, true);
222+
}
223+
224+
/// <summary>
225+
/// Enforces that the given set does not contain a certain member
226+
/// </summary>
227+
public static Condition SetNotContains(RedisKey key, RedisValue member)
228+
{
229+
return new ExistsCondition(key, RedisType.Set, member, false);
230+
}
231+
216232
/// <summary>
217233
/// Enforces that the given sorted set cardinality is a certain value
218234
/// </summary>
@@ -237,6 +253,22 @@ public static Condition SortedSetLengthGreaterThan(RedisKey key, long length)
237253
return new LengthCondition(key, RedisType.SortedSet, -1, length);
238254
}
239255

256+
/// <summary>
257+
/// Enforces that the given sorted set contains a certain member
258+
/// </summary>
259+
public static Condition SortedSetContains(RedisKey key, RedisValue member)
260+
{
261+
return new ExistsCondition(key, RedisType.SortedSet, member, true);
262+
}
263+
264+
/// <summary>
265+
/// Enforces that the given sorted set does not contain a certain member
266+
/// </summary>
267+
public static Condition SortedSetNotContains(RedisKey key, RedisValue member)
268+
{
269+
return new ExistsCondition(key, RedisType.SortedSet, member, false);
270+
}
271+
240272
internal abstract void CheckCommands(CommandMap commandMap);
241273

242274
internal abstract IEnumerable<Message> CreateMessages(int db, ResultBox resultBox);
@@ -298,38 +330,62 @@ internal override void WriteImpl(PhysicalConnection physical)
298330
internal class ExistsCondition : Condition
299331
{
300332
private readonly bool expectedResult;
301-
private readonly RedisValue hashField;
333+
private readonly RedisValue expectedValue;
302334
private readonly RedisKey key;
335+
private readonly RedisType type;
336+
private readonly RedisCommand cmd;
303337

304338
internal override Condition MapKeys(Func<RedisKey,RedisKey> map)
305339
{
306-
return new ExistsCondition(map(key), hashField, expectedResult);
340+
return new ExistsCondition(map(key), type, expectedValue, expectedResult);
307341
}
308-
public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
342+
public ExistsCondition(RedisKey key, RedisType type, RedisValue expectedValue, bool expectedResult)
309343
{
310344
if (key.IsNull) throw new ArgumentException("key");
311345
this.key = key;
312-
this.hashField = hashField;
346+
this.type = type;
347+
this.expectedValue = expectedValue;
313348
this.expectedResult = expectedResult;
349+
350+
if (expectedValue.IsNull) {
351+
cmd = RedisCommand.EXISTS;
352+
}
353+
else {
354+
switch (type) {
355+
case RedisType.Hash:
356+
cmd = RedisCommand.HEXISTS;
357+
break;
358+
359+
case RedisType.Set:
360+
cmd = RedisCommand.SISMEMBER;
361+
break;
362+
363+
case RedisType.SortedSet:
364+
cmd = RedisCommand.ZSCORE;
365+
break;
366+
367+
default:
368+
throw new ArgumentException(nameof(type));
369+
}
370+
}
314371
}
315372

316373
public override string ToString()
317374
{
318-
return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
375+
return (expectedValue.IsNull ? key.ToString() : ((string)key) + " " + type + " > " + expectedValue)
319376
+ (expectedResult ? " exists" : " does not exists");
320377
}
321378

322379
internal override void CheckCommands(CommandMap commandMap)
323380
{
324-
commandMap.AssertAvailable(hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS);
381+
commandMap.AssertAvailable(cmd);
325382
}
326383

327384
internal override IEnumerable<Message> CreateMessages(int db, ResultBox resultBox)
328385
{
329386
yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key);
330387

331-
var cmd = hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS;
332-
var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, hashField);
388+
var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, expectedValue);
333389
message.SetSource(ConditionProcessor.Default, resultBox);
334390
yield return message;
335391
}
@@ -340,15 +396,24 @@ internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrateg
340396
}
341397
internal override bool TryValidate(RawResult result, out bool value)
342398
{
343-
bool parsed;
344-
if (ResultProcessor.DemandZeroOrOneProcessor.TryGet(result, out parsed))
345-
{
346-
value = parsed == expectedResult;
347-
ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value);
348-
return true;
399+
switch (type) {
400+
case RedisType.SortedSet:
401+
var parsedValue = result.AsRedisValue();
402+
value = (parsedValue.IsNull != expectedResult);
403+
ConnectionMultiplexer.TraceWithoutContext("exists: " + parsedValue + "; expected: " + expectedResult + "; voting: " + value);
404+
return true;
405+
406+
default:
407+
bool parsed;
408+
if (ResultProcessor.DemandZeroOrOneProcessor.TryGet(result, out parsed))
409+
{
410+
value = parsed == expectedResult;
411+
ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value);
412+
return true;
413+
}
414+
value = false;
415+
return false;
349416
}
350-
value = false;
351-
return false;
352417
}
353418
}
354419

0 commit comments

Comments
 (0)