@@ -30,6 +30,91 @@ private struct ZRangeOptions
30
30
public bool WithScores { get ; set ; }
31
31
} ;
32
32
33
+ bool TryGetSortedSetAddOption ( ReadOnlySpan < byte > item , out SortedSetAddOption options )
34
+ {
35
+ if ( item . EqualsUpperCaseSpanIgnoringCase ( "XX"u8 ) )
36
+ {
37
+ options = SortedSetAddOption . XX ;
38
+ return true ;
39
+ }
40
+ if ( item . EqualsUpperCaseSpanIgnoringCase ( "NX"u8 ) )
41
+ {
42
+ options = SortedSetAddOption . NX ;
43
+ return true ;
44
+ }
45
+ if ( item . EqualsUpperCaseSpanIgnoringCase ( "LT"u8 ) )
46
+ {
47
+ options = SortedSetAddOption . LT ;
48
+ return true ;
49
+ }
50
+ if ( item . EqualsUpperCaseSpanIgnoringCase ( "GT"u8 ) )
51
+ {
52
+ options = SortedSetAddOption . GT ;
53
+ return true ;
54
+ }
55
+ if ( item . EqualsUpperCaseSpanIgnoringCase ( "CH"u8 ) )
56
+ {
57
+ options = SortedSetAddOption . CH ;
58
+ return true ;
59
+ }
60
+ if ( item . EqualsUpperCaseSpanIgnoringCase ( "INCR"u8 ) )
61
+ {
62
+ options = SortedSetAddOption . INCR ;
63
+ return true ;
64
+ }
65
+ options = SortedSetAddOption . None ;
66
+ return false ;
67
+ }
68
+
69
+ bool GetOptions ( ref ObjectInput input , ref int currTokenIdx , out SortedSetAddOption options , ref byte * curr , byte * end , ref SpanByteAndMemory output , ref bool isMemory , ref byte * ptr , ref MemoryHandle ptrHandle )
70
+ {
71
+ options = SortedSetAddOption . None ;
72
+
73
+ while ( currTokenIdx < input . parseState . Count )
74
+ {
75
+ if ( ! TryGetSortedSetAddOption ( input . parseState . GetArgSliceByRef ( currTokenIdx ) . ReadOnlySpan , out var currOption ) )
76
+ break ;
77
+
78
+ options |= currOption ;
79
+ currTokenIdx ++ ;
80
+ }
81
+
82
+ // Validate ZADD options combination
83
+ ReadOnlySpan < byte > optionsError = default ;
84
+
85
+ // XX & NX are mutually exclusive
86
+ if ( options . HasFlag ( SortedSetAddOption . XX ) && options . HasFlag ( SortedSetAddOption . NX ) )
87
+ optionsError = CmdStrings . RESP_ERR_XX_NX_NOT_COMPATIBLE ;
88
+
89
+ // NX, GT & LT are mutually exclusive
90
+ if ( ( options . HasFlag ( SortedSetAddOption . GT ) && options . HasFlag ( SortedSetAddOption . LT ) ) ||
91
+ ( ( options . HasFlag ( SortedSetAddOption . GT ) || options . HasFlag ( SortedSetAddOption . LT ) ) &&
92
+ options . HasFlag ( SortedSetAddOption . NX ) ) )
93
+ optionsError = CmdStrings . RESP_ERR_GT_LT_NX_NOT_COMPATIBLE ;
94
+
95
+ // INCR supports only one score-element pair
96
+ if ( options . HasFlag ( SortedSetAddOption . INCR ) && ( input . parseState . Count - currTokenIdx > 2 ) )
97
+ optionsError = CmdStrings . RESP_ERR_INCR_SUPPORTS_ONLY_SINGLE_PAIR ;
98
+
99
+ if ( ! optionsError . IsEmpty )
100
+ {
101
+ while ( ! RespWriteUtils . WriteError ( optionsError , ref curr , end ) )
102
+ ObjectUtils . ReallocateOutput ( ref output , ref isMemory , ref ptr , ref ptrHandle , ref curr , ref end ) ;
103
+ return false ;
104
+ }
105
+
106
+ // From here on we expect only score-element pairs
107
+ // Remaining token count should be positive and even
108
+ if ( currTokenIdx == input . parseState . Count || ( input . parseState . Count - currTokenIdx ) % 2 != 0 )
109
+ {
110
+ while ( ! RespWriteUtils . WriteError ( CmdStrings . RESP_SYNTAX_ERROR , ref curr , end ) )
111
+ ObjectUtils . ReallocateOutput ( ref output , ref isMemory , ref ptr , ref ptrHandle , ref curr , ref end ) ;
112
+ return false ;
113
+ }
114
+
115
+ return true ;
116
+ }
117
+
33
118
private void SortedSetAdd ( ref ObjectInput input , ref SpanByteAndMemory output )
34
119
{
35
120
var isMemory = false ;
@@ -46,60 +131,34 @@ private void SortedSetAdd(ref ObjectInput input, ref SpanByteAndMemory output)
46
131
try
47
132
{
48
133
var options = SortedSetAddOption . None ;
49
-
50
134
var currTokenIdx = input . parseStateStartIdx ;
51
- while ( currTokenIdx < input . parseState . Count )
52
- {
53
- if ( ! input . parseState . TryGetEnum ( currTokenIdx , true , out SortedSetAddOption currOption ) )
54
- break ;
55
-
56
- options |= currOption ;
57
- currTokenIdx ++ ;
58
- }
59
-
60
- // Validate ZADD options combination
61
- ReadOnlySpan < byte > optionsError = default ;
62
-
63
- // XX & NX are mutually exclusive
64
- if ( options . HasFlag ( SortedSetAddOption . XX ) && options . HasFlag ( SortedSetAddOption . NX ) )
65
- optionsError = CmdStrings . RESP_ERR_XX_NX_NOT_COMPATIBLE ;
66
-
67
- // NX, GT & LT are mutually exclusive
68
- if ( ( options . HasFlag ( SortedSetAddOption . GT ) && options . HasFlag ( SortedSetAddOption . LT ) ) ||
69
- ( ( options . HasFlag ( SortedSetAddOption . GT ) || options . HasFlag ( SortedSetAddOption . LT ) ) &&
70
- options . HasFlag ( SortedSetAddOption . NX ) ) )
71
- optionsError = CmdStrings . RESP_ERR_GT_LT_NX_NOT_COMPATIBLE ;
72
-
73
- // INCR supports only one score-element pair
74
- if ( options . HasFlag ( SortedSetAddOption . INCR ) && ( input . parseState . Count - currTokenIdx > 2 ) )
75
- optionsError = CmdStrings . RESP_ERR_INCR_SUPPORTS_ONLY_SINGLE_PAIR ;
76
-
77
- if ( ! optionsError . IsEmpty )
78
- {
79
- while ( ! RespWriteUtils . WriteError ( optionsError , ref curr , end ) )
80
- ObjectUtils . ReallocateOutput ( ref output , ref isMemory , ref ptr , ref ptrHandle , ref curr , ref end ) ;
81
- return ;
82
- }
83
-
84
- // From here on we expect only score-element pairs
85
- // Remaining token count should be positive and even
86
- if ( currTokenIdx == input . parseState . Count || ( input . parseState . Count - currTokenIdx ) % 2 != 0 )
87
- {
88
- while ( ! RespWriteUtils . WriteError ( CmdStrings . RESP_SYNTAX_ERROR , ref curr , end ) )
89
- ObjectUtils . ReallocateOutput ( ref output , ref isMemory , ref ptr , ref ptrHandle , ref curr , ref end ) ;
90
- return ;
91
- }
135
+ var parsedOptions = false ;
92
136
93
137
while ( currTokenIdx < input . parseState . Count )
94
138
{
95
- // Score
96
- if ( ! input . parseState . TryGetDouble ( currTokenIdx ++ , out var score ) )
139
+ // Try to parse a Score field
140
+ if ( ! input . parseState . TryGetDouble ( currTokenIdx , out var score ) )
97
141
{
98
- while ( ! RespWriteUtils . WriteError ( CmdStrings . RESP_ERR_NOT_VALID_FLOAT , ref curr , end ) )
99
- ObjectUtils . ReallocateOutput ( ref output , ref isMemory , ref ptr , ref ptrHandle , ref curr , ref end ) ;
100
- return ;
142
+ // Try to get and validate options before the Score field, if any
143
+ if ( ! parsedOptions )
144
+ {
145
+ parsedOptions = true ;
146
+ if ( ! GetOptions ( ref input , ref currTokenIdx , out options , ref curr , end , ref output , ref isMemory , ref ptr , ref ptrHandle ) )
147
+ return ;
148
+ continue ; // retry after parsing options
149
+ }
150
+ else
151
+ {
152
+ // Invalid Score encountered
153
+ while ( ! RespWriteUtils . WriteError ( CmdStrings . RESP_ERR_NOT_VALID_FLOAT , ref curr , end ) )
154
+ ObjectUtils . ReallocateOutput ( ref output , ref isMemory , ref ptr , ref ptrHandle , ref curr , ref end ) ;
155
+ return ;
156
+ }
101
157
}
102
158
159
+ parsedOptions = true ;
160
+ currTokenIdx ++ ;
161
+
103
162
// Member
104
163
var memberSpan = input . parseState . GetArgSliceByRef ( currTokenIdx ++ ) . ReadOnlySpan ;
105
164
var member = memberSpan . ToArray ( ) ;
0 commit comments