Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,29 @@ private static ReadOnlySequence<byte> BuildFilledSequence(
return new ReadOnlySequence<byte>(segments[0].Memory[..lastLength]);
}

var firstMemory = segments[0];
var contiguousLength = firstMemory.Length;
var contiguous = true;

for (var i = 1; i < usedChunks; i++)
{
var segment = segments[i];

if (!ReferenceEquals(segment.Buffer, firstMemory.Buffer)
|| segment.Offset != firstMemory.Offset + contiguousLength)
{
contiguous = false;
break;
}

contiguousLength += i == usedChunks - 1 ? lastLength : segment.Length;
}

if (contiguous)
{
return new ReadOnlySequence<byte>(firstMemory.Buffer, firstMemory.Offset, contiguousLength);
}

SequenceSegment? first = null;
SequenceSegment? previous = null;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System.Text;
using HotChocolate.Buffers;

namespace HotChocolate.Fusion.Text.Json;

public class SourceResultDocumentSequenceTests
{
[Theory]
[InlineData("adjacent", 1024, false)]
[InlineData("adjacent", 1024, true)]
[InlineData("adjacent", 1025, false)]
[InlineData("adjacent", 3072, true)]
[InlineData("adjacent", 32768, false)]
[InlineData("gapped", 1025, false)]
[InlineData("gapped", 3072, true)]
[InlineData("gapped", 32768, false)]
[InlineData("separate", 1025, false)]
[InlineData("separate", 3072, true)]
[InlineData("separate", 32768, false)]
public void ParseFilled_Should_PreserveValues_When_ChunksUseDifferentLayouts(
string layout,
int payloadLength,
bool emptyLast)
{
// arrange
const string prefix = "{\"escaped\\\"name\":\"";
const string suffix = "\\n\\u00E9\",\"number\":123456789}";
var padding = new string('a', payloadLength - prefix.Length - suffix.Length);
var payload = prefix + padding + suffix;
using var arena = new MemoryArena();
var (segments, usedChunks, lastLength) = CreateSegments(payload, layout, emptyLast);

// act
using var document = SourceResultDocument.ParseFilled(arena, segments, usedChunks, lastLength);
arena.Seal();

// assert
Assert.Equal(payload, document.Root.GetRawText());
Assert.Equal(padding + "\né", document.Root.GetProperty("escaped\"name").GetString());
Assert.Equal(123456789, document.Root.GetProperty("number").GetInt32());
}

[Theory]
[InlineData("adjacent")]
[InlineData("gapped")]
[InlineData("separate")]
public void ParseFilledElement_Should_PreserveOffsets_When_ReaderContainsMultipleResults(string layout)
{
// arrange
var firstPayload = "{\"value\":\"" + new string('a', 1500) + "\"}";
var secondPayload = "{\"value\":\"" + new string('b', 2000) + "\"}";
var payload = "[" + firstPayload + "," + secondPayload + "]";
using var arena = new MemoryArena();
var (segments, usedChunks, lastLength) = CreateSegments(payload, layout, emptyLast: false);
var reader = SourceResultDocument.CreateFilledReader(segments, usedChunks, lastLength);
reader.Read();
reader.Read();

// act
using var first = SourceResultDocument.ParseFilledElement(arena, ref reader, segments, usedChunks);
reader.Read();
using var second = SourceResultDocument.ParseFilledElement(arena, ref reader, segments, usedChunks);
arena.Seal();

// assert
Assert.Equal(firstPayload, first.Root.GetRawText());
Assert.Equal(secondPayload, second.Root.GetRawText());
}

private static (MemorySegment[] Segments, int UsedChunks, int LastLength) CreateSegments(
string payload,
string layout,
bool emptyLast)
{
var bytes = Encoding.UTF8.GetBytes(payload);
var backing = new byte[128 * 1024];
var segments = new MemorySegment[64];
var offset = 17;
var consumed = 0;
var usedChunks = 0;
var lastLength = 0;
while (consumed < bytes.Length || emptyLast)
{
var chunkSize = SourceResultDocument.GetDataChunkSize(usedChunks);
var segment = layout == "separate"
? new MemorySegment(new byte[chunkSize + 3], 3, chunkSize)
: new MemorySegment(backing, offset, chunkSize);
segments[usedChunks++] = segment;
lastLength = Math.Min(chunkSize, bytes.Length - consumed);
bytes.AsSpan(consumed, lastLength).CopyTo(segment.Span);
consumed += lastLength;
offset += chunkSize + (layout == "gapped" ? 3 : 0);
if (lastLength < chunkSize)
{
emptyLast = false;
}
}
return (segments, usedChunks, lastLength);
}
}
Loading