-
Notifications
You must be signed in to change notification settings - Fork 0
Add Set support #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Havret
wants to merge
1
commit into
main
Choose a base branch
from
Add-Set-Support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add Set support #46
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| using System.Buffers; | ||
|
|
||
| namespace RocksDb.Extensions; | ||
|
|
||
| /// <summary> | ||
| /// Base class for serializing collections of fixed-size elements like primitive types (int, long, etc.) | ||
| /// where each element occupies the same number of bytes in memory. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The serialized format consists of: | ||
| /// - 4 bytes: Collection length (number of elements) | ||
| /// - Remaining bytes: Contiguous array of serialized elements | ||
| /// </remarks> | ||
| /// <typeparam name="TCollection">The collection type (e.g., IList{T}, ISet{T})</typeparam> | ||
| /// <typeparam name="TElement">The element type</typeparam> | ||
| internal abstract class FixedSizeCollectionSerializer<TCollection, TElement> : ISerializer<TCollection> | ||
| where TCollection : ICollection<TElement> | ||
| { | ||
| private readonly ISerializer<TElement> _scalarSerializer; | ||
|
|
||
| protected FixedSizeCollectionSerializer(ISerializer<TElement> scalarSerializer) | ||
| { | ||
| _scalarSerializer = scalarSerializer; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a new collection instance with the specified capacity. | ||
| /// </summary> | ||
| protected abstract TCollection CreateCollection(int capacity); | ||
|
|
||
| /// <summary> | ||
| /// Adds an element to the collection. | ||
| /// </summary> | ||
| protected abstract void AddElement(TCollection collection, TElement element); | ||
|
|
||
| public bool TryCalculateSize(ref TCollection value, out int size) | ||
| { | ||
| size = sizeof(int); // size of the collection | ||
| if (value.Count == 0) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| var referentialElement = value.First(); | ||
| if (_scalarSerializer.TryCalculateSize(ref referentialElement, out var elementSize)) | ||
| { | ||
| size += value.Count * elementSize; | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| public void WriteTo(ref TCollection value, ref Span<byte> span) | ||
| { | ||
| // Write the size of the collection | ||
| var slice = span.Slice(0, sizeof(int)); | ||
| BitConverter.TryWriteBytes(slice, value.Count); | ||
|
|
||
| // Write the elements of the collection | ||
| int offset = sizeof(int); | ||
| var elementSize = (span.Length - offset) / value.Count; | ||
| foreach (var item in value) | ||
| { | ||
| var element = item; | ||
| slice = span.Slice(offset, elementSize); | ||
| _scalarSerializer.WriteTo(ref element, ref slice); | ||
| offset += elementSize; | ||
| } | ||
| } | ||
|
|
||
| public void WriteTo(ref TCollection value, IBufferWriter<byte> buffer) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| public TCollection Deserialize(ReadOnlySpan<byte> buffer) | ||
| { | ||
| // Read the size of the collection | ||
| var slice = buffer.Slice(0, sizeof(int)); | ||
| var size = BitConverter.ToInt32(slice); | ||
|
|
||
| var collection = CreateCollection(size); | ||
|
|
||
| // Read the elements of the collection | ||
| int offset = sizeof(int); | ||
| var elementSize = (buffer.Length - offset) / size; | ||
|
||
| for (int i = 0; i < size; i++) | ||
| { | ||
| slice = buffer.Slice(offset, elementSize); | ||
| var element = _scalarSerializer.Deserialize(slice); | ||
| AddElement(collection, element); | ||
| offset += elementSize; | ||
| } | ||
|
|
||
| return collection; | ||
| } | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,87 +1,12 @@ | ||
| using System.Buffers; | ||
|
|
||
| namespace RocksDb.Extensions; | ||
|
|
||
| /// <summary> | ||
| /// Serializes lists of fixed-size elements like primitive types (int, long, etc.) where each element | ||
| /// occupies the same number of bytes in memory. This implementation optimizes for performance by | ||
| /// pre-calculating buffer sizes based on element count. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Use this serializer when working with lists of primitive types or structs where all elements | ||
| /// have identical size. The serialized format consists of: | ||
| /// - 4 bytes: List length (number of elements) | ||
| /// - Remaining bytes: Contiguous array of serialized elements | ||
| /// </remarks> | ||
| internal class FixedSizeListSerializer<T> : ISerializer<IList<T>> | ||
| internal class FixedSizeListSerializer<T> : FixedSizeCollectionSerializer<IList<T>, T> | ||
| { | ||
| private readonly ISerializer<T> _scalarSerializer; | ||
|
|
||
| public FixedSizeListSerializer(ISerializer<T> scalarSerializer) | ||
| { | ||
| _scalarSerializer = scalarSerializer; | ||
| } | ||
|
|
||
| public bool TryCalculateSize(ref IList<T> value, out int size) | ||
| public FixedSizeListSerializer(ISerializer<T> scalarSerializer) : base(scalarSerializer) | ||
| { | ||
| size = sizeof(int); // size of the list | ||
| if (value.Count == 0) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| var referentialElement = value[0]; | ||
| if (_scalarSerializer.TryCalculateSize(ref referentialElement, out var elementSize)) | ||
| { | ||
| size += value.Count * elementSize; | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| public void WriteTo(ref IList<T> value, ref Span<byte> span) | ||
| { | ||
| // Write the size of the list | ||
| var slice = span.Slice(0, sizeof(int)); | ||
| BitConverter.TryWriteBytes(slice, value.Count); | ||
|
|
||
| // Write the elements of the list | ||
| int offset = sizeof(int); | ||
| var elementSize = (span.Length - offset) / value.Count; | ||
| for (int i = 0; i < value.Count; i++) | ||
| { | ||
| var element = value[i]; | ||
| slice = span.Slice(offset, elementSize); | ||
| _scalarSerializer.WriteTo(ref element, ref slice); | ||
| offset += elementSize; | ||
| } | ||
| } | ||
| protected override IList<T> CreateCollection(int capacity) => new List<T>(capacity); | ||
|
|
||
| public void WriteTo(ref IList<T> value, IBufferWriter<byte> buffer) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| public IList<T> Deserialize(ReadOnlySpan<byte> buffer) | ||
| { | ||
| // Read the size of the list | ||
| var slice = buffer.Slice(0, sizeof(int)); | ||
| var size = BitConverter.ToInt32(slice); | ||
|
|
||
| var list = new List<T>(size); | ||
|
|
||
| // Read the elements of the list | ||
| int offset = sizeof(int); | ||
| var elementSize = (buffer.Length - offset) / size; | ||
| for (int i = 0; i < size; i++) | ||
| { | ||
| slice = buffer.Slice(offset, elementSize); | ||
| var element = _scalarSerializer.Deserialize(slice); | ||
| list.Add(element); | ||
| offset += elementSize; | ||
| } | ||
|
|
||
| return list; | ||
| } | ||
| protected override void AddElement(IList<T> collection, T element) => collection.Add(element); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| namespace RocksDb.Extensions; | ||
|
|
||
| internal class FixedSizeSetSerializer<T> : FixedSizeCollectionSerializer<ISet<T>, T> | ||
| { | ||
| public FixedSizeSetSerializer(ISerializer<T> scalarSerializer) : base(scalarSerializer) | ||
| { | ||
| } | ||
|
|
||
| protected override ISet<T> CreateCollection(int capacity) => new HashSet<T>(capacity); | ||
|
|
||
| protected override void AddElement(ISet<T> collection, T element) => collection.Add(element); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
src/RocksDb.Extensions/VariableSizeCollectionSerializer.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| using System.Buffers; | ||
|
|
||
| namespace RocksDb.Extensions; | ||
|
|
||
| /// <summary> | ||
| /// Base class for serializing collections containing variable-size elements like strings or complex objects | ||
| /// where each element may occupy a different number of bytes when serialized. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The serialized format consists of: | ||
| /// - 4 bytes: Collection length (number of elements) | ||
| /// - For each element: | ||
| /// - 4 bytes: Size of the serialized element | ||
| /// - N bytes: Serialized element data | ||
| /// </remarks> | ||
| /// <typeparam name="TCollection">The collection type (e.g., IList{T}, ISet{T})</typeparam> | ||
| /// <typeparam name="TElement">The element type</typeparam> | ||
| internal abstract class VariableSizeCollectionSerializer<TCollection, TElement> : ISerializer<TCollection> | ||
| where TCollection : ICollection<TElement> | ||
| { | ||
| private readonly ISerializer<TElement> _scalarSerializer; | ||
|
|
||
| protected VariableSizeCollectionSerializer(ISerializer<TElement> scalarSerializer) | ||
| { | ||
| _scalarSerializer = scalarSerializer; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a new collection instance with the specified capacity. | ||
| /// </summary> | ||
| protected abstract TCollection CreateCollection(int capacity); | ||
|
|
||
| /// <summary> | ||
| /// Adds an element to the collection. | ||
| /// </summary> | ||
| protected abstract void AddElement(TCollection collection, TElement element); | ||
|
|
||
| public bool TryCalculateSize(ref TCollection value, out int size) | ||
| { | ||
| size = sizeof(int); // size of the collection | ||
| if (value.Count == 0) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| foreach (var item in value) | ||
| { | ||
| var element = item; | ||
| if (_scalarSerializer.TryCalculateSize(ref element, out var elementSize)) | ||
| { | ||
| size += sizeof(int); | ||
| size += elementSize; | ||
| } | ||
| else | ||
| { | ||
| // Element serializer can't calculate size, so we can't either | ||
| size = 0; | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| public void WriteTo(ref TCollection value, ref Span<byte> span) | ||
| { | ||
| // Write the size of the collection | ||
| var slice = span.Slice(0, sizeof(int)); | ||
| BitConverter.TryWriteBytes(slice, value.Count); | ||
|
|
||
| // Write the elements of the collection | ||
| int offset = sizeof(int); | ||
| foreach (var item in value) | ||
| { | ||
| var element = item; | ||
| if (_scalarSerializer.TryCalculateSize(ref element, out var elementSize)) | ||
| { | ||
| slice = span.Slice(offset, sizeof(int)); | ||
| BitConverter.TryWriteBytes(slice, elementSize); | ||
| offset += sizeof(int); | ||
|
|
||
| slice = span.Slice(offset, elementSize); | ||
| _scalarSerializer.WriteTo(ref element, ref slice); | ||
| offset += elementSize; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public void WriteTo(ref TCollection value, IBufferWriter<byte> buffer) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| public TCollection Deserialize(ReadOnlySpan<byte> buffer) | ||
| { | ||
| // Read the size of the collection | ||
| var slice = buffer.Slice(0, sizeof(int)); | ||
| var size = BitConverter.ToInt32(slice); | ||
|
|
||
| var collection = CreateCollection(size); | ||
|
|
||
| // Read the elements of the collection | ||
| int offset = sizeof(int); | ||
| for (int i = 0; i < size; i++) | ||
| { | ||
| slice = buffer.Slice(offset, sizeof(int)); | ||
| var elementSize = BitConverter.ToInt32(slice); | ||
| offset += sizeof(int); | ||
|
|
||
| slice = buffer.Slice(offset, elementSize); | ||
| var element = _scalarSerializer.Deserialize(slice); | ||
| AddElement(collection, element); | ||
| offset += elementSize; | ||
| } | ||
|
|
||
| return collection; | ||
| } | ||
| } | ||
|
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Division by zero will occur when value.Count is 0. While TryCalculateSize handles empty collections correctly, WriteTo does not check if the collection is empty before dividing. This will cause a DivideByZeroException when serializing an empty collection. Add a check to return early when value.Count is 0.