11extern alias respite ;
2+ using System ;
23using System . Globalization ;
34using respite ::RESPite . Messages ;
45using StackExchange . Redis . Server ;
56using Xunit ;
67
78namespace StackExchange . Redis . Tests ;
89
9- public class IncrexTestServer ( StringIncrementResult < string > expectedResult , ITestOutputHelper ? log = null ) : InProcessTestServer ( log )
10+ public class IncrexTestServer ( ITestOutputHelper ? log = null ) : InProcessTestServer ( log )
1011{
1112 public sealed class IncrexRequestSnapshot
1213 {
@@ -25,11 +26,17 @@ public sealed class IncrexRequestSnapshot
2526 [ RedisCommand ( - 4 , "INCREX" ) ]
2627 protected virtual TypedRedisValue Increx ( RedisClient client , in RedisRequest request )
2728 {
28- var snapshot = new IncrexRequestSnapshot
29- {
30- Key = request . GetKey ( 1 ) ,
31- } ;
29+ var snapshot = ParseRequest ( in request ) ;
30+ LastRequest = snapshot ;
3231
32+ return snapshot . IsFloat
33+ ? ExecuteDouble ( client . Database , snapshot )
34+ : ExecuteInt64 ( client . Database , snapshot ) ;
35+ }
36+
37+ private IncrexRequestSnapshot ParseRequest ( in RedisRequest request )
38+ {
39+ var snapshot = new IncrexRequestSnapshot { Key = request . GetKey ( 1 ) } ;
3340 int index = 2 ;
3441 while ( index < request . Count )
3542 {
@@ -61,12 +68,101 @@ protected virtual TypedRedisValue Increx(RedisClient client, in RedisRequest req
6168 break ;
6269 }
6370 }
71+ return snapshot ;
72+ }
6473
65- LastRequest = snapshot ;
74+ private TypedRedisValue ExecuteInt64 ( int database , IncrexRequestSnapshot snapshot )
75+ {
76+ var raw = Get ( database , snapshot . Key ) ;
77+ bool existed = ! raw . IsNull ;
78+ long current = raw . IsNull ? 0 : ( long ) raw ;
79+ long delta = long . Parse ( snapshot . Increment , CultureInfo . InvariantCulture ) ;
80+ long ? lowerBound = snapshot . LowerBound is null ? null : long . Parse ( snapshot . LowerBound , CultureInfo . InvariantCulture ) ;
81+ long ? upperBound = snapshot . UpperBound is null ? null : long . Parse ( snapshot . UpperBound , CultureInfo . InvariantCulture ) ;
82+
83+ long next = current ;
84+ long applied = 0 ;
85+
86+ try
87+ {
88+ long candidate = checked ( current + delta ) ;
89+ if ( ( ! lowerBound . HasValue || candidate >= lowerBound . GetValueOrDefault ( ) )
90+ && ( ! upperBound . HasValue || candidate <= upperBound . GetValueOrDefault ( ) ) )
91+ {
92+ next = candidate ;
93+ applied = delta ;
94+ }
95+ }
96+ catch ( OverflowException ) { }
97+
98+ ApplyValueAndExpiry ( database , snapshot , existed , next ) ;
99+ return MakeResult ( next , applied ) ;
100+ }
66101
102+ private TypedRedisValue ExecuteDouble ( int database , IncrexRequestSnapshot snapshot )
103+ {
104+ var raw = Get ( database , snapshot . Key ) ;
105+ bool existed = ! raw . IsNull ;
106+ double current = raw . IsNull ? 0D : ( double ) raw ;
107+ double delta = double . Parse ( snapshot . Increment , CultureInfo . InvariantCulture ) ;
108+ double ? lowerBound = snapshot . LowerBound is null ? null : double . Parse ( snapshot . LowerBound , CultureInfo . InvariantCulture ) ;
109+ double ? upperBound = snapshot . UpperBound is null ? null : double . Parse ( snapshot . UpperBound , CultureInfo . InvariantCulture ) ;
110+
111+ double next = current ;
112+ double applied = 0 ;
113+
114+ double candidate = current + delta ;
115+ if ( ( ! lowerBound . HasValue || candidate >= lowerBound . GetValueOrDefault ( ) )
116+ && ( ! upperBound . HasValue || candidate <= upperBound . GetValueOrDefault ( ) ) )
117+ {
118+ next = candidate ;
119+ applied = delta ;
120+ }
121+
122+ ApplyValueAndExpiry ( database , snapshot , existed , next ) ;
123+ return MakeResult ( next , applied ) ;
124+ }
125+
126+ private void ApplyValueAndExpiry ( int database , IncrexRequestSnapshot snapshot , bool existed , RedisValue value )
127+ {
128+ var priorTtl = existed ? Ttl ( database , snapshot . Key ) : null ;
129+ Set ( database , snapshot . Key , value ) ;
130+
131+ if ( snapshot . ExpiryMode is null )
132+ {
133+ return ;
134+ }
135+
136+ if ( snapshot . Enx && priorTtl . HasValue && priorTtl . Value != TimeSpan . MaxValue )
137+ {
138+ _ = Expire ( database , snapshot . Key , priorTtl . Value ) ;
139+ return ;
140+ }
141+
142+ var ttl = snapshot . ExpiryMode switch
143+ {
144+ "EX" => TimeSpan . FromSeconds ( long . Parse ( snapshot . ExpiryValue ! , CultureInfo . InvariantCulture ) ) ,
145+ "PX" => TimeSpan . FromMilliseconds ( long . Parse ( snapshot . ExpiryValue ! , CultureInfo . InvariantCulture ) ) ,
146+ "EXAT" => DateTimeOffset . FromUnixTimeSeconds ( long . Parse ( snapshot . ExpiryValue ! , CultureInfo . InvariantCulture ) ) . UtcDateTime - Time ( ) ,
147+ "PXAT" => DateTimeOffset . FromUnixTimeMilliseconds ( long . Parse ( snapshot . ExpiryValue ! , CultureInfo . InvariantCulture ) ) . UtcDateTime - Time ( ) ,
148+ _ => throw new InvalidOperationException ( "Unknown expiry mode: " + snapshot . ExpiryMode ) ,
149+ } ;
150+ _ = Expire ( database , snapshot . Key , ttl ) ;
151+ }
152+
153+ private static TypedRedisValue MakeResult ( long value , long appliedIncrement )
154+ {
155+ var result = TypedRedisValue . Rent ( 2 , out var span , RespPrefix . Array ) ;
156+ span [ 0 ] = TypedRedisValue . BulkString ( ( RedisValue ) value ) ;
157+ span [ 1 ] = TypedRedisValue . BulkString ( ( RedisValue ) appliedIncrement ) ;
158+ return result ;
159+ }
160+
161+ private static TypedRedisValue MakeResult ( double value , double appliedIncrement )
162+ {
67163 var result = TypedRedisValue . Rent ( 2 , out var span , RespPrefix . Array ) ;
68- span [ 0 ] = TypedRedisValue . BulkString ( expectedResult . Value ) ;
69- span [ 1 ] = TypedRedisValue . BulkString ( expectedResult . AppliedIncrement ) ;
164+ span [ 0 ] = TypedRedisValue . BulkString ( ( RedisValue ) value ) ;
165+ span [ 1 ] = TypedRedisValue . BulkString ( ( RedisValue ) appliedIncrement ) ;
70166 return result ;
71167 }
72168
0 commit comments