Skip to content
Draft
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
64 changes: 64 additions & 0 deletions LiteDB.Tests/Internals/BufferWriter_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,70 @@ public void Buffer_Write_Types()
p += PageAddress.SIZE;
}

[Fact]
public void Buffer_Write_Guid_ObjectId_Across_Segments()
{
var guid = new Guid("01020304-0506-0708-090A-0B0C0D0E0F10");
var objectId = new ObjectId(0x11223344, 0x556677, 0x6677, 0xAABBCC);

var slices = new[]
{
new BufferSlice(new byte[8], 0, 8),
new BufferSlice(new byte[10], 0, 10),
new BufferSlice(new byte[12], 0, 12)
};

using (var writer = new BufferWriter(slices))
{
writer.Write(guid);
writer.Write(objectId);
}

var expectedGuidBytes = guid.ToByteArray();
var actualGuidBytes = new byte[16];

Buffer.BlockCopy(slices[0].Array, slices[0].Offset, actualGuidBytes, 0, slices[0].Count);
Buffer.BlockCopy(slices[1].Array, slices[1].Offset, actualGuidBytes, slices[0].Count, 16 - slices[0].Count);

actualGuidBytes.Should().Equal(expectedGuidBytes);

var expectedObjectIdBytes = objectId.ToByteArray();
var actualObjectIdBytes = new byte[12];

Buffer.BlockCopy(slices[1].Array, slices[1].Offset + 16 - slices[0].Count, actualObjectIdBytes, 0, slices[1].Count - (16 - slices[0].Count));
Buffer.BlockCopy(slices[2].Array, slices[2].Offset, actualObjectIdBytes, slices[1].Count - (16 - slices[0].Count), 12 - (slices[1].Count - (16 - slices[0].Count)));

actualObjectIdBytes.Should().Equal(expectedObjectIdBytes);

using (var reader = new BufferReader(slices))
{
reader.ReadGuid().Should().Be(guid);
reader.ReadObjectId().Should().Be(objectId);
}
}

[Fact]
public void BufferSlice_Span_Based_Guid_ObjectId_Should_Preserve_Endianness()
{
var buffer = new BufferSlice(new byte[64], 4, 40);
var guid = new Guid("0F0E0D0C-0B0A-0908-0706-050403020100");
var objectId = new ObjectId(0x0A0B0C0D, 0x010203, 0x0405, 0x060708);

buffer.Write(guid, 3);
buffer.Write(objectId, 21);

buffer.ReadGuid(3).Should().Be(guid);
buffer.ReadObjectId(21).Should().Be(objectId);

var guidBytes = new byte[16];
Buffer.BlockCopy(buffer.Array, buffer.Offset + 3, guidBytes, 0, 16);
guidBytes.Should().Equal(guid.ToByteArray());

var objectIdBytes = new byte[12];
Buffer.BlockCopy(buffer.Array, buffer.Offset + 21, objectIdBytes, 0, 12);
objectIdBytes.Should().Equal(objectId.ToByteArray());
}

[Fact]
public void Buffer_Write_Overflow()
{
Expand Down
40 changes: 32 additions & 8 deletions LiteDB/Engine/Disk/Serializer/BufferReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using static LiteDB.Constants;

namespace LiteDB.Engine
Expand Down Expand Up @@ -124,6 +125,28 @@ public int Read(byte[] buffer, int offset, int count)
return bufferPosition;
}

private void Read(Span<byte> destination)
{
var written = 0;

while (written < destination.Length)
{
var bytesLeft = _current.Count - _currentPosition;
var bytesToCopy = Math.Min(destination.Length - written, bytesLeft);

new ReadOnlySpan<byte>(_current.Array, _current.Offset + _currentPosition, bytesToCopy)
.CopyTo(destination.Slice(written, bytesToCopy));

written += bytesToCopy;

this.MoveForward(bytesToCopy);

if (_isEOF) break;
}

ENSURE(written == destination.Length, "current value must fit inside defined buffer");
}

/// <summary>
/// Skip bytes (same as Read but with no array copy)
/// </summary>
Expand Down Expand Up @@ -245,8 +268,11 @@ public Guid ReadGuid()
}
else
{
// can't use _tempoBuffer because Guid validate 16 bytes array length
value = new Guid(this.ReadBytes(16));
Span<byte> buffer = stackalloc byte[16];

this.Read(buffer);

value = MemoryMarshal.Read<Guid>(buffer);
}

return value;
Expand All @@ -261,19 +287,17 @@ public ObjectId ReadObjectId()

if (_currentPosition + 12 <= _current.Count)
{
value = new ObjectId(_current.Array, _current.Offset + _currentPosition);
value = BufferSliceExtensions.ReadObjectId(new ReadOnlySpan<byte>(_current.Array, _current.Offset + _currentPosition, 12));

this.MoveForward(12);
}
else
{
var buffer = _bufferPool.Rent(12);
Span<byte> buffer = stackalloc byte[12];

this.Read(buffer, 0, 12);
this.Read(buffer);

value = new ObjectId(buffer, 0);

_bufferPool.Return(buffer, true);
value = BufferSliceExtensions.ReadObjectId(buffer);
}

return value;
Expand Down
71 changes: 61 additions & 10 deletions LiteDB/Engine/Disk/Serializer/BufferWriter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static LiteDB.Constants;

namespace LiteDB.Engine
Expand Down Expand Up @@ -125,6 +127,28 @@ public int Write(byte[] buffer, int offset, int count)
/// </summary>
public int Write(byte[] buffer) => this.Write(buffer, 0, buffer.Length);

private void Write(ReadOnlySpan<byte> source)
{
var written = 0;

while (written < source.Length)
{
var bytesLeft = _current.Count - _currentPosition;
var bytesToCopy = Math.Min(source.Length - written, bytesLeft);

source.Slice(written, bytesToCopy)
.CopyTo(new Span<byte>(_current.Array, _current.Offset + _currentPosition, bytesToCopy));

written += bytesToCopy;

this.MoveForward(bytesToCopy);

if (_isEOF) break;
}

ENSURE(written == source.Length, "current value must fit inside defined buffer");
}

/// <summary>
/// Skip bytes (same as Write but with no array copy)
/// </summary>
Expand Down Expand Up @@ -217,10 +241,36 @@ public void Write(DateTime value)
/// </summary>
public void Write(Guid value)
{
// there is no avaiable value.TryWriteBytes (TODO: implement conditional compile)?
var bytes = value.ToByteArray();
if (_currentPosition + 16 <= _current.Count)
{
var span = new Span<byte>(_current.Array, _current.Offset + _currentPosition, 16);

this.Write(bytes, 0, 16);
#if NET8_0_OR_GREATER
if (!value.TryWriteBytes(span))
{
throw new InvalidOperationException("Failed to write Guid into span.");
}
#else
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(span), value);
#endif

this.MoveForward(16);
}
else
{
Span<byte> buffer = stackalloc byte[16];

#if NET8_0_OR_GREATER
if (!value.TryWriteBytes(buffer))
{
throw new InvalidOperationException("Failed to write Guid into span.");
}
#else
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(buffer), value);
#endif

this.Write(buffer);
}
}

/// <summary>
Expand All @@ -230,19 +280,19 @@ public void Write(ObjectId value)
{
if (_currentPosition + 12 <= _current.Count)
{
value.ToByteArray(_current.Array, _current.Offset + _currentPosition);
var span = new Span<byte>(_current.Array, _current.Offset + _currentPosition, 12);

BufferSliceExtensions.Write(span, value);

this.MoveForward(12);
}
else
{
var buffer = _bufferPool.Rent(12);
Span<byte> buffer = stackalloc byte[12];

value.ToByteArray(buffer, 0);
BufferSliceExtensions.Write(buffer, value);

this.Write(buffer, 0, 12);

_bufferPool.Return(buffer, true);
this.Write(buffer);
}
}

Expand Down Expand Up @@ -444,3 +494,4 @@ public void Dispose()
}
}
}

5 changes: 3 additions & 2 deletions LiteDB/LiteDB.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
Expand Down Expand Up @@ -63,9 +63,10 @@

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Memory" Version="4.5.0" />
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>

<!-- End References -->

</Project>

78 changes: 74 additions & 4 deletions LiteDB/Utils/Extensions/BufferSliceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static LiteDB.Constants;

namespace LiteDB
Expand Down Expand Up @@ -66,12 +68,48 @@ public static Decimal ReadDecimal(this BufferSlice buffer, int offset)

public static ObjectId ReadObjectId(this BufferSlice buffer, int offset)
{
return new ObjectId(buffer.Array, buffer.Offset + offset);
var span = new ReadOnlySpan<byte>(buffer.Array, buffer.Offset + offset, 12);

return ReadObjectId(span);
}

public static Guid ReadGuid(this BufferSlice buffer, int offset)
{
return new Guid(buffer.ReadBytes(offset, 16));
var span = new ReadOnlySpan<byte>(buffer.Array, buffer.Offset + offset, 16);

return ReadGuid(span);
}

internal static ObjectId ReadObjectId(ReadOnlySpan<byte> span)
{
ENSURE(span.Length >= 12, "span must contain at least 12 bytes");

var timestamp =
(span[0] << 24) |
(span[1] << 16) |
(span[2] << 8) |
span[3];

var machine =
(span[4] << 16) |
(span[5] << 8) |
span[6];

var pid = (short)((span[7] << 8) | span[8]);

var increment =
(span[9] << 16) |
(span[10] << 8) |
span[11];

return new ObjectId(timestamp, machine, pid, increment);
}

internal static Guid ReadGuid(ReadOnlySpan<byte> span)
{
ENSURE(span.Length >= 16, "span must contain at least 16 bytes");

return MemoryMarshal.Read<Guid>(span);
}

public static byte[] ReadBytes(this BufferSlice buffer, int offset, int count)
Expand Down Expand Up @@ -256,7 +294,16 @@ public static void Write(this BufferSlice buffer, PageAddress value, int offset)

public static void Write(this BufferSlice buffer, Guid value, int offset)
{
buffer.Write(value.ToByteArray(), offset);
var span = new Span<byte>(buffer.Array, buffer.Offset + offset, 16);

#if NET8_0_OR_GREATER
if (!value.TryWriteBytes(span))
{
throw new InvalidOperationException("Failed to write Guid into span.");
}
#else
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(span), value);
#endif
}

public static void Write(this BufferSlice buffer, float[] value, int offset)
Expand All @@ -272,7 +319,30 @@ public static void Write(this BufferSlice buffer, float[] value, int offset)

public static void Write(this BufferSlice buffer, ObjectId value, int offset)
{
value.ToByteArray(buffer.Array, buffer.Offset + offset);
var span = new Span<byte>(buffer.Array, buffer.Offset + offset, 12);

Write(span, value);
}

internal static void Write(Span<byte> destination, ObjectId value)
{
ENSURE(destination.Length >= 12, "span must contain at least 12 bytes");

destination[0] = (byte)(value.Timestamp >> 24);
destination[1] = (byte)(value.Timestamp >> 16);
destination[2] = (byte)(value.Timestamp >> 8);
destination[3] = (byte)(value.Timestamp);

destination[4] = (byte)(value.Machine >> 16);
destination[5] = (byte)(value.Machine >> 8);
destination[6] = (byte)(value.Machine);

destination[7] = (byte)(value.Pid >> 8);
destination[8] = (byte)(value.Pid);

destination[9] = (byte)(value.Increment >> 16);
destination[10] = (byte)(value.Increment >> 8);
destination[11] = (byte)(value.Increment);
}

public static void Write(this BufferSlice buffer, byte[] value, int offset)
Expand Down
Loading