@@ -284,6 +284,20 @@ public static Condition StringNotEqual(RedisKey key, RedisValue value)
284284 /// <param name="member">The member the sorted set must not contain.</param>
285285 public static Condition SortedSetNotContains ( RedisKey key , RedisValue member ) => new ExistsCondition ( key , RedisType . SortedSet , member , false ) ;
286286
287+ /// <summary>
288+ /// Enforces that the given sorted set contains a member that starts with the specified prefix.
289+ /// </summary>
290+ /// <param name="key">The key of the sorted set to check.</param>
291+ /// <param name="prefix">The sorted set must contain at least one member that starts with the specified prefix.</param>
292+ public static Condition SortedSetContainsStarting ( RedisKey key , RedisValue prefix ) => new StartsWithCondition ( key , prefix , true ) ;
293+
294+ /// <summary>
295+ /// Enforces that the given sorted set does not contain a member that starts with the specified prefix.
296+ /// </summary>
297+ /// <param name="key">The key of the sorted set to check.</param>
298+ /// <param name="prefix">The sorted set must not contain at a member that starts with the specified prefix.</param>
299+ public static Condition SortedSetNotContainsStarting ( RedisKey key , RedisValue prefix ) => new StartsWithCondition ( key , prefix , false ) ;
300+
287301 /// <summary>
288302 /// Enforces that the given sorted set member must have the specified score.
289303 /// </summary>
@@ -370,6 +384,9 @@ public static Message CreateMessage(Condition condition, int db, CommandFlags fl
370384 public static Message CreateMessage ( Condition condition , int db , CommandFlags flags , RedisCommand command , in RedisKey key , in RedisValue value , in RedisValue value1 ) =>
371385 new ConditionMessage ( condition , db , flags , command , key , value , value1 ) ;
372386
387+ public static Message CreateMessage ( Condition condition , int db , CommandFlags flags , RedisCommand command , in RedisKey key , in RedisValue value , in RedisValue value1 , in RedisValue value2 , in RedisValue value3 , in RedisValue value4 ) =>
388+ new ConditionMessage ( condition , db , flags , command , key , value , value1 , value2 , value3 , value4 ) ;
389+
373390 [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE0071:Simplify interpolation" , Justification = "Allocations (string.Concat vs. string.Format)" ) ]
374391 protected override bool SetResultCore ( PhysicalConnection connection , Message message , in RawResult result )
375392 {
@@ -389,6 +406,9 @@ private sealed class ConditionMessage : Message.CommandKeyBase
389406 public readonly Condition Condition ;
390407 private readonly RedisValue value ;
391408 private readonly RedisValue value1 ;
409+ private readonly RedisValue value2 ;
410+ private readonly RedisValue value3 ;
411+ private readonly RedisValue value4 ;
392412
393413 public ConditionMessage ( Condition condition , int db , CommandFlags flags , RedisCommand command , in RedisKey key , in RedisValue value )
394414 : base ( db , flags , command , key )
@@ -403,6 +423,15 @@ public ConditionMessage(Condition condition, int db, CommandFlags flags, RedisCo
403423 this . value1 = value1 ; // note no assert here
404424 }
405425
426+ // Message with 3 or 4 values not used, therefore not implemented
427+ public ConditionMessage ( Condition condition , int db , CommandFlags flags , RedisCommand command , in RedisKey key , in RedisValue value , in RedisValue value1 , in RedisValue value2 , in RedisValue value3 , in RedisValue value4 )
428+ : this ( condition , db , flags , command , key , value , value1 )
429+ {
430+ this . value2 = value2 ; // note no assert here
431+ this . value3 = value3 ; // note no assert here
432+ this . value4 = value4 ; // note no assert here
433+ }
434+
406435 protected override void WriteImpl ( PhysicalConnection physical )
407436 {
408437 if ( value . IsNull )
@@ -412,16 +441,20 @@ protected override void WriteImpl(PhysicalConnection physical)
412441 }
413442 else
414443 {
415- physical . WriteHeader ( command , value1 . IsNull ? 2 : 3 ) ;
444+ physical . WriteHeader ( command , value1 . IsNull ? 2 : value2 . IsNull ? 3 : value3 . IsNull ? 4 : value4 . IsNull ? 5 : 6 ) ;
416445 physical . Write ( Key ) ;
417446 physical . WriteBulkString ( value ) ;
418447 if ( ! value1 . IsNull )
419- {
420448 physical . WriteBulkString ( value1 ) ;
421- }
449+ if ( ! value2 . IsNull )
450+ physical . WriteBulkString ( value2 ) ;
451+ if ( ! value3 . IsNull )
452+ physical . WriteBulkString ( value3 ) ;
453+ if ( ! value4 . IsNull )
454+ physical . WriteBulkString ( value4 ) ;
422455 }
423456 }
424- public override int ArgCount => value . IsNull ? 1 : value1 . IsNull ? 2 : 3 ;
457+ public override int ArgCount => value . IsNull ? 1 : value1 . IsNull ? 2 : value2 . IsNull ? 3 : value3 . IsNull ? 4 : value4 . IsNull ? 5 : 6 ;
425458 }
426459 }
427460
@@ -501,6 +534,67 @@ internal override bool TryValidate(in RawResult result, out bool value)
501534 }
502535 }
503536
537+ internal sealed class StartsWithCondition : Condition
538+ {
539+ /* only usable for RedisType.SortedSet, members of SortedSets are always byte-arrays, expectedStartValue therefore is a byte-array
540+ any Encoding and Conversion for the search-sequence has to be executed in calling application
541+ working with byte arrays should prevent any encoding within this class, that could distort the comparison */
542+
543+ private readonly bool expectedResult ;
544+ private readonly RedisValue prefix ;
545+ private readonly RedisKey key ;
546+
547+ internal override Condition MapKeys ( Func < RedisKey , RedisKey > map ) =>
548+ new StartsWithCondition ( map ( key ) , prefix , expectedResult ) ;
549+
550+ public StartsWithCondition ( in RedisKey key , in RedisValue prefix , bool expectedResult )
551+ {
552+ if ( key . IsNull ) throw new ArgumentNullException ( nameof ( key ) ) ;
553+ if ( prefix . IsNull ) throw new ArgumentNullException ( nameof ( prefix ) ) ;
554+ this . key = key ;
555+ this . prefix = prefix ;
556+ this . expectedResult = expectedResult ;
557+ }
558+
559+ public override string ToString ( ) =>
560+ $ "{ key } { nameof ( RedisType . SortedSet ) } > { ( expectedResult ? " member starting " : " no member starting " ) } { prefix } + prefix";
561+
562+ internal override void CheckCommands ( CommandMap commandMap ) => commandMap . AssertAvailable ( RedisCommand . ZRANGEBYLEX ) ;
563+
564+ internal override IEnumerable < Message > CreateMessages ( int db , IResultBox ? resultBox )
565+ {
566+ yield return Message . Create ( db , CommandFlags . None , RedisCommand . WATCH , key ) ;
567+
568+ // prepend '[' to prefix for inclusive search
569+ var startValueWithToken = RedisDatabase . GetLexRange ( prefix , Exclude . None , isStart : true , Order . Ascending ) ;
570+
571+ var message = ConditionProcessor . CreateMessage (
572+ this ,
573+ db ,
574+ CommandFlags . None ,
575+ RedisCommand . ZRANGEBYLEX ,
576+ key ,
577+ startValueWithToken ,
578+ RedisLiterals . PlusSymbol ,
579+ RedisLiterals . LIMIT ,
580+ 0 ,
581+ 1 ) ;
582+
583+ message . SetSource ( ConditionProcessor . Default , resultBox ) ;
584+ yield return message ;
585+ }
586+
587+ internal override int GetHashSlot ( ServerSelectionStrategy serverSelectionStrategy ) => serverSelectionStrategy . HashSlot ( key ) ;
588+
589+ internal override bool TryValidate ( in RawResult result , out bool value )
590+ {
591+ value = result . ItemsCount == 1 && result [ 0 ] . AsRedisValue ( ) . StartsWith ( prefix ) ;
592+
593+ if ( ! expectedResult ) value = ! value ;
594+ return true ;
595+ }
596+ }
597+
504598 internal sealed class EqualsCondition : Condition
505599 {
506600 internal override Condition MapKeys ( Func < RedisKey , RedisKey > map ) =>
0 commit comments