Skip to content

Commit ec2571e

Browse files
committed
TryParse
1 parent df5904e commit ec2571e

3 files changed

Lines changed: 62 additions & 3 deletions

File tree

src/RESPite/Shared/FrameworkShims.Sequence.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ namespace System.Buffers
44
{
55
internal static class FrameworkSequenceShims
66
{
7-
// by-value receiver (not 'in'): the returned span references the underlying heap segment, not the
8-
// sequence struct, so it must not be scoped to the receiver - matching the BCL FirstSpan semantics
9-
extension<T>(ReadOnlySequence<T> sequence)
7+
extension<T>(scoped in ReadOnlySequence<T> sequence)
108
{
119
/// <summary>
1210
/// Gets the first segment of the sequence as a span. On modern runtimes this is a BCL instance

src/StackExchange.Redis/RedisValue.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,22 @@ internal ReadOnlySequence<byte> RawSequence()
284284
return default;
285285
}
286286

287+
// Linearizes a Sequence payload into the supplied buffer (which must be at least _length long),
288+
// walking the segments directly via the iterator - i.e. without paying to build a ReadOnlySequence -
289+
// and returns the populated portion of the buffer.
290+
private ReadOnlySpan<byte> CopyRawSequence(Span<byte> destination)
291+
{
292+
var iterator = RawSequenceIterator();
293+
int offset = 0;
294+
while (iterator.TryNext(out var memory))
295+
{
296+
memory.Span.CopyTo(destination.Slice(offset));
297+
offset += memory.Length;
298+
}
299+
Debug.Assert(offset == _length, "linearized length mismatch");
300+
return destination.Slice(0, offset);
301+
}
302+
287303
internal ReadOnlySpan<byte> RawSpan()
288304
{
289305
if (_obj is byte[] b) return new ReadOnlySpan<byte>(b, _index, _length);
@@ -1346,6 +1362,15 @@ public bool TryParse(out long val)
13461362
return Format.TryParseInt64(RawString(), out val);
13471363
case StorageType.MemoryManager or StorageType.ByteArray:
13481364
return Format.TryParseInt64(RawSpan(), out val);
1365+
case StorageType.Sequence:
1366+
// longer than the largest possible Int64 text => cannot be an Int64; otherwise
1367+
// linearize onto the stack and reuse the span-based parse (matching the ByteArray path)
1368+
if (_length <= Format.MaxInt64TextLen)
1369+
{
1370+
Span<byte> buffer = stackalloc byte[Format.MaxInt64TextLen];
1371+
return Format.TryParseInt64(CopyRawSequence(buffer), out val);
1372+
}
1373+
break;
13491374
case StorageType.Double:
13501375
var d = OverlappedValueDouble;
13511376
try
@@ -1406,6 +1431,15 @@ public bool TryParse(out double val)
14061431
return Format.TryParseDouble(RawString(), out val);
14071432
case StorageType.MemoryManager or StorageType.ByteArray:
14081433
return TryParseDouble(RawSpan(), out val);
1434+
case StorageType.Sequence:
1435+
// longer than the largest possible double text => cannot be a double; otherwise
1436+
// linearize onto the stack and reuse the span-based parse (matching the ByteArray path)
1437+
if (_length <= Format.MaxDoubleTextLen)
1438+
{
1439+
Span<byte> buffer = stackalloc byte[Format.MaxDoubleTextLen];
1440+
return TryParseDouble(CopyRawSequence(buffer), out val);
1441+
}
1442+
break;
14091443
case StorageType.Null:
14101444
// in redis-land 0 approx. equal null; so roll with it
14111445
val = 0;

tests/StackExchange.Redis.Tests/RedisValueSequenceTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,33 @@ public void IntegerAndTextForms_HashIdentically()
125125
Assert.Equal(expected, asSequence.GetHashCode());
126126
}
127127

128+
[Theory]
129+
[InlineData("123")] // integer
130+
[InlineData("-123")] // negative integer
131+
[InlineData("00")] // leading zeros, within length limit
132+
[InlineData("123.5")] // non-integer double
133+
[InlineData("-0.25")] // negative double
134+
[InlineData("abc")] // not numeric at all
135+
[InlineData("12x")] // partially numeric (must not parse)
136+
[InlineData("99999999999999999999999")] // oversize: cannot be Int64 or double-as-int
137+
public void MultiSegmentSequence_TryParse_MatchesByteArray(string text)
138+
{
139+
var bytes = Encoding.UTF8.GetBytes(text);
140+
RedisValue asBytes = bytes; // single-buffer (ByteArray)
141+
RedisValue asSequence = SplitEveryByte(bytes); // multi-buffer (Sequence)
142+
Assert.Equal(RedisValue.StorageType.Sequence, asSequence.Type);
143+
144+
// a sequence-backed value must parse exactly like the equivalent byte[]
145+
Assert.Equal(asBytes.TryParse(out long expectedLong), asSequence.TryParse(out long actualLong));
146+
Assert.Equal(expectedLong, actualLong);
147+
148+
Assert.Equal(asBytes.TryParse(out int expectedInt), asSequence.TryParse(out int actualInt));
149+
Assert.Equal(expectedInt, actualInt);
150+
151+
Assert.Equal(asBytes.TryParse(out double expectedDouble), asSequence.TryParse(out double actualDouble));
152+
Assert.Equal(expectedDouble, actualDouble);
153+
}
154+
128155
[Fact]
129156
public void MultiSegmentBytes_RoundTripToArray()
130157
{

0 commit comments

Comments
 (0)