Reformat / cleanup
All checks were successful
NuGet Publish / build-and-pack (push) Successful in 46s
NuGet Publish / publish-to-gitea (push) Successful in 56s

This commit is contained in:
Joseph Doherty
2026-02-21 08:10:36 -05:00
parent 4c6aaa5a3f
commit a70d8befae
176 changed files with 50555 additions and 49587 deletions

View File

@@ -1,60 +1,64 @@
using System;
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// Represents an in-memory BSON document with lazy parsing.
/// Uses Memory&lt;byte&gt; to store raw BSON data for zero-copy operations.
/// </summary>
public sealed class BsonDocument
{
using System.Collections.Concurrent;
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// Represents an in-memory BSON document with lazy parsing.
/// Uses Memory&lt;byte&gt; to store raw BSON data for zero-copy operations.
/// </summary>
public sealed class BsonDocument
{
private readonly ConcurrentDictionary<ushort, string>? _keys;
private readonly Memory<byte> _rawData;
private readonly System.Collections.Concurrent.ConcurrentDictionary<ushort, string>? _keys;
/// <summary>
/// Initializes a new instance of the <see cref="BsonDocument"/> class from raw BSON memory.
/// Initializes a new instance of the <see cref="BsonDocument" /> class from raw BSON memory.
/// </summary>
/// <param name="rawBsonData">The raw BSON data.</param>
/// <param name="keys">The optional key dictionary.</param>
public BsonDocument(Memory<byte> rawBsonData, System.Collections.Concurrent.ConcurrentDictionary<ushort, string>? keys = null)
public BsonDocument(Memory<byte> rawBsonData, ConcurrentDictionary<ushort, string>? keys = null)
{
_rawData = rawBsonData;
_keys = keys;
}
/// <summary>
/// Initializes a new instance of the <see cref="BsonDocument"/> class from raw BSON bytes.
/// Initializes a new instance of the <see cref="BsonDocument" /> class from raw BSON bytes.
/// </summary>
/// <param name="rawBsonData">The raw BSON data.</param>
/// <param name="keys">The optional key dictionary.</param>
public BsonDocument(byte[] rawBsonData, System.Collections.Concurrent.ConcurrentDictionary<ushort, string>? keys = null)
public BsonDocument(byte[] rawBsonData, ConcurrentDictionary<ushort, string>? keys = null)
{
_rawData = rawBsonData;
_keys = keys;
}
/// <summary>
/// Gets the raw BSON bytes
/// </summary>
public ReadOnlySpan<byte> RawData => _rawData.Span;
/// <summary>
/// Gets the document size in bytes
/// </summary>
public int Size => BitConverter.ToInt32(_rawData.Span[..4]);
/// <summary>
/// Creates a reader for this document
/// </summary>
public BsonSpanReader GetReader() => new BsonSpanReader(_rawData.Span, _keys ?? new System.Collections.Concurrent.ConcurrentDictionary<ushort, string>());
}
/// <summary>
/// Tries to get a field value by name.
/// Returns false if field not found.
/// Gets the raw BSON bytes
/// </summary>
public ReadOnlySpan<byte> RawData => _rawData.Span;
/// <summary>
/// Gets the document size in bytes
/// </summary>
public int Size => BitConverter.ToInt32(_rawData.Span[..4]);
/// <summary>
/// Creates a reader for this document
/// </summary>
public BsonSpanReader GetReader()
{
return new BsonSpanReader(_rawData.Span,
_keys ?? new ConcurrentDictionary<ushort, string>());
}
/// <summary>
/// Tries to get a field value by name.
/// Returns false if field not found.
/// </summary>
/// <param name="fieldName">The field name.</param>
/// <param name="value">When this method returns, contains the field value if found; otherwise <see langword="null"/>.</param>
/// <returns><see langword="true"/> if the field is found; otherwise, <see langword="false"/>.</returns>
/// <param name="value">When this method returns, contains the field value if found; otherwise <see langword="null" />.</param>
/// <returns><see langword="true" /> if the field is found; otherwise, <see langword="false" />.</returns>
public bool TryGetString(string fieldName, out string? value)
{
value = null;
@@ -66,30 +70,30 @@ public sealed class BsonDocument
fieldName = fieldName.ToLowerInvariant();
while (reader.Remaining > 1)
{
var type = reader.ReadBsonType();
if (type == BsonType.EndOfDocument)
break;
var name = reader.ReadElementHeader();
var type = reader.ReadBsonType();
if (type == BsonType.EndOfDocument)
break;
string name = reader.ReadElementHeader();
if (name == fieldName && type == BsonType.String)
{
value = reader.ReadString();
return true;
}
reader.SkipValue(type);
}
return false;
}
if (name == fieldName && type == BsonType.String)
{
value = reader.ReadString();
return true;
}
reader.SkipValue(type);
}
return false;
}
/// <summary>
/// Tries to get an Int32 field value by name.
/// Tries to get an Int32 field value by name.
/// </summary>
/// <param name="fieldName">The field name.</param>
/// <param name="value">When this method returns, contains the field value if found; otherwise zero.</param>
/// <returns><see langword="true"/> if the field is found; otherwise, <see langword="false"/>.</returns>
/// <returns><see langword="true" /> if the field is found; otherwise, <see langword="false" />.</returns>
public bool TryGetInt32(string fieldName, out int value)
{
value = 0;
@@ -101,30 +105,30 @@ public sealed class BsonDocument
fieldName = fieldName.ToLowerInvariant();
while (reader.Remaining > 1)
{
var type = reader.ReadBsonType();
if (type == BsonType.EndOfDocument)
break;
var name = reader.ReadElementHeader();
var type = reader.ReadBsonType();
if (type == BsonType.EndOfDocument)
break;
string name = reader.ReadElementHeader();
if (name == fieldName && type == BsonType.Int32)
{
value = reader.ReadInt32();
return true;
}
reader.SkipValue(type);
}
return false;
}
if (name == fieldName && type == BsonType.Int32)
{
value = reader.ReadInt32();
return true;
}
reader.SkipValue(type);
}
return false;
}
/// <summary>
/// Tries to get an ObjectId field value by name.
/// Tries to get an ObjectId field value by name.
/// </summary>
/// <param name="fieldName">The field name.</param>
/// <param name="value">When this method returns, contains the field value if found; otherwise default.</param>
/// <returns><see langword="true"/> if the field is found; otherwise, <see langword="false"/>.</returns>
/// <returns><see langword="true" /> if the field is found; otherwise, <see langword="false" />.</returns>
public bool TryGetObjectId(string fieldName, out ObjectId value)
{
value = default;
@@ -136,52 +140,53 @@ public sealed class BsonDocument
fieldName = fieldName.ToLowerInvariant();
while (reader.Remaining > 1)
{
var type = reader.ReadBsonType();
if (type == BsonType.EndOfDocument)
break;
var name = reader.ReadElementHeader();
var type = reader.ReadBsonType();
if (type == BsonType.EndOfDocument)
break;
string name = reader.ReadElementHeader();
if (name == fieldName && type == BsonType.ObjectId)
{
value = reader.ReadObjectId();
return true;
}
reader.SkipValue(type);
}
return false;
}
if (name == fieldName && type == BsonType.ObjectId)
{
value = reader.ReadObjectId();
return true;
}
reader.SkipValue(type);
}
return false;
}
/// <summary>
/// Creates a new BsonDocument from field values using a builder pattern
/// Creates a new BsonDocument from field values using a builder pattern
/// </summary>
/// <param name="keyMap">The key map used for field name encoding.</param>
/// <param name="buildAction">The action that populates the builder.</param>
/// <returns>The created BSON document.</returns>
public static BsonDocument Create(System.Collections.Concurrent.ConcurrentDictionary<string, ushort> keyMap, Action<BsonDocumentBuilder> buildAction)
public static BsonDocument Create(ConcurrentDictionary<string, ushort> keyMap,
Action<BsonDocumentBuilder> buildAction)
{
var builder = new BsonDocumentBuilder(keyMap);
buildAction(builder);
return builder.Build();
}
}
/// <summary>
/// Builder for creating BSON documents
/// </summary>
return builder.Build();
}
}
/// <summary>
/// Builder for creating BSON documents
/// </summary>
public sealed class BsonDocumentBuilder
{
private byte[] _buffer = new byte[1024]; // Start with 1KB
private int _position;
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, ushort> _keyMap;
private readonly ConcurrentDictionary<string, ushort> _keyMap;
private byte[] _buffer = new byte[1024]; // Start with 1KB
private int _position;
/// <summary>
/// Initializes a new instance of the <see cref="BsonDocumentBuilder"/> class.
/// Initializes a new instance of the <see cref="BsonDocumentBuilder" /> class.
/// </summary>
/// <param name="keyMap">The key map used for field name encoding.</param>
public BsonDocumentBuilder(System.Collections.Concurrent.ConcurrentDictionary<string, ushort> keyMap)
public BsonDocumentBuilder(ConcurrentDictionary<string, ushort> keyMap)
{
_keyMap = keyMap;
var writer = new BsonSpanWriter(_buffer, _keyMap);
@@ -189,7 +194,7 @@ public sealed class BsonDocumentBuilder
}
/// <summary>
/// Adds a string field to the document.
/// Adds a string field to the document.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The field value.</param>
@@ -204,7 +209,7 @@ public sealed class BsonDocumentBuilder
}
/// <summary>
/// Adds an Int32 field to the document.
/// Adds an Int32 field to the document.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The field value.</param>
@@ -213,13 +218,13 @@ public sealed class BsonDocumentBuilder
{
EnsureCapacity(64);
var writer = new BsonSpanWriter(_buffer.AsSpan(_position..), _keyMap);
writer.WriteInt32(name, value);
writer.WriteInt32(name, value);
_position += writer.Position;
return this;
}
/// <summary>
/// Adds an Int64 field to the document.
/// Adds an Int64 field to the document.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The field value.</param>
@@ -228,13 +233,13 @@ public sealed class BsonDocumentBuilder
{
EnsureCapacity(64);
var writer = new BsonSpanWriter(_buffer.AsSpan(_position..), _keyMap);
writer.WriteInt64(name, value);
writer.WriteInt64(name, value);
_position += writer.Position;
return this;
}
/// <summary>
/// Adds a Boolean field to the document.
/// Adds a Boolean field to the document.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The field value.</param>
@@ -243,13 +248,13 @@ public sealed class BsonDocumentBuilder
{
EnsureCapacity(64);
var writer = new BsonSpanWriter(_buffer.AsSpan(_position..), _keyMap);
writer.WriteBoolean(name, value);
writer.WriteBoolean(name, value);
_position += writer.Position;
return this;
}
/// <summary>
/// Adds an ObjectId field to the document.
/// Adds an ObjectId field to the document.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The field value.</param>
@@ -258,19 +263,19 @@ public sealed class BsonDocumentBuilder
{
EnsureCapacity(64);
var writer = new BsonSpanWriter(_buffer.AsSpan(_position..), _keyMap);
writer.WriteObjectId(name, value);
writer.WriteObjectId(name, value);
_position += writer.Position;
return this;
}
/// <summary>
/// Builds a BSON document from the accumulated fields.
/// Builds a BSON document from the accumulated fields.
/// </summary>
/// <returns>The constructed BSON document.</returns>
public BsonDocument Build()
{
// Layout: [int32 size][field bytes...][0x00 terminator]
var totalSize = _position + 5;
int totalSize = _position + 5;
var finalBuffer = new byte[totalSize];
BitConverter.TryWriteBytes(finalBuffer.AsSpan(0, 4), totalSize);
@@ -279,14 +284,14 @@ public sealed class BsonDocumentBuilder
return new BsonDocument(finalBuffer);
}
private void EnsureCapacity(int additional)
{
if (_position + additional > _buffer.Length)
{
var newBuffer = new byte[_buffer.Length * 2];
_buffer.CopyTo(newBuffer, 0);
_buffer = newBuffer;
}
}
}
private void EnsureCapacity(int additional)
{
if (_position + additional > _buffer.Length)
{
var newBuffer = new byte[_buffer.Length * 2];
_buffer.CopyTo(newBuffer, 0);
_buffer = newBuffer;
}
}
}

View File

@@ -1,7 +1,7 @@
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// BSON type codes as defined in BSON spec
/// BSON type codes as defined in BSON spec
/// </summary>
public enum BsonType : byte
{
@@ -27,4 +27,4 @@ public enum BsonType : byte
Decimal128 = 0x13,
MinKey = 0xFF,
MaxKey = 0x7F
}
}

View File

@@ -1,262 +1,263 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Text;
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// BSON writer that serializes to an IBufferWriter, enabling streaming serialization
/// without fixed buffer size limits.
/// </summary>
using System.Buffers;
using System.Buffers.Binary;
using System.Text;
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// BSON writer that serializes to an IBufferWriter, enabling streaming serialization
/// without fixed buffer size limits.
/// </summary>
public ref struct BsonBufferWriter
{
private IBufferWriter<byte> _writer;
private int _totalBytesWritten;
private readonly IBufferWriter<byte> _writer;
/// <summary>
/// Initializes a new instance of the <see cref="BsonBufferWriter"/> struct.
/// Initializes a new instance of the <see cref="BsonBufferWriter" /> struct.
/// </summary>
/// <param name="writer">The buffer writer to write BSON bytes to.</param>
public BsonBufferWriter(IBufferWriter<byte> writer)
{
_writer = writer;
_totalBytesWritten = 0;
Position = 0;
}
/// <summary>
/// Gets the current write position in bytes.
/// Gets the current write position in bytes.
/// </summary>
public int Position => _totalBytesWritten;
public int Position { get; private set; }
private void WriteBytes(ReadOnlySpan<byte> data)
{
var destination = _writer.GetSpan(data.Length);
data.CopyTo(destination);
_writer.Advance(data.Length);
_totalBytesWritten += data.Length;
private void WriteBytes(ReadOnlySpan<byte> data)
{
var destination = _writer.GetSpan(data.Length);
data.CopyTo(destination);
_writer.Advance(data.Length);
Position += data.Length;
}
private void WriteByte(byte value)
{
var span = _writer.GetSpan(1);
span[0] = value;
_writer.Advance(1);
_totalBytesWritten++;
private void WriteByte(byte value)
{
var span = _writer.GetSpan(1);
span[0] = value;
_writer.Advance(1);
Position++;
}
/// <summary>
/// Writes a BSON date-time field.
/// Writes a BSON date-time field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The date-time value.</param>
public void WriteDateTime(string name, DateTime value)
{
WriteByte((byte)BsonType.DateTime);
WriteCString(name);
// BSON DateTime: milliseconds since Unix epoch (UTC)
var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var milliseconds = (long)(value.ToUniversalTime() - unixEpoch).TotalMilliseconds;
WriteInt64Internal(milliseconds);
{
WriteByte((byte)BsonType.DateTime);
WriteCString(name);
// BSON DateTime: milliseconds since Unix epoch (UTC)
var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var milliseconds = (long)(value.ToUniversalTime() - unixEpoch).TotalMilliseconds;
WriteInt64Internal(milliseconds);
}
/// <summary>
/// Begins writing a BSON document.
/// Begins writing a BSON document.
/// </summary>
/// <returns>The position where the document size placeholder was written.</returns>
public int BeginDocument()
{
// Write placeholder for size (4 bytes)
var sizePosition = _totalBytesWritten;
var span = _writer.GetSpan(4);
// Initialize with default value (will be patched later)
span[0] = 0; span[1] = 0; span[2] = 0; span[3] = 0;
_writer.Advance(4);
_totalBytesWritten += 4;
return sizePosition;
{
// Write placeholder for size (4 bytes)
int sizePosition = Position;
var span = _writer.GetSpan(4);
// Initialize with default value (will be patched later)
span[0] = 0;
span[1] = 0;
span[2] = 0;
span[3] = 0;
_writer.Advance(4);
Position += 4;
return sizePosition;
}
/// <summary>
/// Ends the current BSON document by writing the document terminator.
/// Ends the current BSON document by writing the document terminator.
/// </summary>
/// <param name="sizePosition">The position of the size placeholder for this document.</param>
public void EndDocument(int sizePosition)
{
// Write document terminator
{
// Write document terminator
WriteByte(0);
// Note: Size patching must be done by caller after accessing WrittenSpan
// from ArrayBufferWriter (or equivalent)
}
}
/// <summary>
/// Begins writing a nested BSON document field.
/// Begins writing a nested BSON document field.
/// </summary>
/// <param name="name">The field name.</param>
/// <returns>The position where the nested document size placeholder was written.</returns>
public int BeginDocument(string name)
{
WriteByte((byte)BsonType.Document);
WriteCString(name);
return BeginDocument();
}
{
WriteByte((byte)BsonType.Document);
WriteCString(name);
return BeginDocument();
}
/// <summary>
/// Begins writing a BSON array field.
/// Begins writing a BSON array field.
/// </summary>
/// <param name="name">The field name.</param>
/// <returns>The position where the array document size placeholder was written.</returns>
public int BeginArray(string name)
{
WriteByte((byte)BsonType.Array);
WriteCString(name);
return BeginDocument();
}
{
WriteByte((byte)BsonType.Array);
WriteCString(name);
return BeginDocument();
}
/// <summary>
/// Ends the current BSON array.
/// Ends the current BSON array.
/// </summary>
/// <param name="sizePosition">The position of the size placeholder for this array.</param>
public void EndArray(int sizePosition)
{
EndDocument(sizePosition);
{
EndDocument(sizePosition);
}
// Private helper methods
private void WriteInt32Internal(int value)
{
var span = _writer.GetSpan(4);
BinaryPrimitives.WriteInt32LittleEndian(span, value);
_writer.Advance(4);
_totalBytesWritten += 4;
private void WriteInt32Internal(int value)
{
var span = _writer.GetSpan(4);
BinaryPrimitives.WriteInt32LittleEndian(span, value);
_writer.Advance(4);
Position += 4;
}
private void WriteInt64Internal(long value)
{
var span = _writer.GetSpan(8);
BinaryPrimitives.WriteInt64LittleEndian(span, value);
_writer.Advance(8);
_totalBytesWritten += 8;
private void WriteInt64Internal(long value)
{
var span = _writer.GetSpan(8);
BinaryPrimitives.WriteInt64LittleEndian(span, value);
_writer.Advance(8);
Position += 8;
}
/// <summary>
/// Writes a BSON ObjectId field.
/// Writes a BSON ObjectId field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The ObjectId value.</param>
public void WriteObjectId(string name, ObjectId value)
{
WriteByte((byte)BsonType.ObjectId);
WriteCString(name);
WriteBytes(value.ToByteArray());
}
{
WriteByte((byte)BsonType.ObjectId);
WriteCString(name);
WriteBytes(value.ToByteArray());
}
/// <summary>
/// Writes a BSON string field.
/// Writes a BSON string field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The string value.</param>
public void WriteString(string name, string value)
{
WriteByte((byte)BsonType.String);
WriteCString(name);
WriteStringValue(value);
{
WriteByte((byte)BsonType.String);
WriteCString(name);
WriteStringValue(value);
}
/// <summary>
/// Writes a BSON boolean field.
/// Writes a BSON boolean field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The boolean value.</param>
public void WriteBoolean(string name, bool value)
{
WriteByte((byte)BsonType.Boolean);
WriteCString(name);
WriteByte((byte)(value ? 1 : 0));
{
WriteByte((byte)BsonType.Boolean);
WriteCString(name);
WriteByte((byte)(value ? 1 : 0));
}
/// <summary>
/// Writes a BSON null field.
/// Writes a BSON null field.
/// </summary>
/// <param name="name">The field name.</param>
public void WriteNull(string name)
{
WriteByte((byte)BsonType.Null);
WriteCString(name);
}
private void WriteStringValue(string value)
{
// String: length (int32) + UTF8 bytes + null terminator
var bytes = Encoding.UTF8.GetBytes(value);
WriteInt32Internal(bytes.Length + 1); // +1 for null terminator
WriteBytes(bytes);
WriteByte(0);
{
WriteByte((byte)BsonType.Null);
WriteCString(name);
}
private void WriteDoubleInternal(double value)
{
var span = _writer.GetSpan(8);
BinaryPrimitives.WriteDoubleLittleEndian(span, value);
_writer.Advance(8);
_totalBytesWritten += 8;
private void WriteStringValue(string value)
{
// String: length (int32) + UTF8 bytes + null terminator
byte[] bytes = Encoding.UTF8.GetBytes(value);
WriteInt32Internal(bytes.Length + 1); // +1 for null terminator
WriteBytes(bytes);
WriteByte(0);
}
private void WriteDoubleInternal(double value)
{
var span = _writer.GetSpan(8);
BinaryPrimitives.WriteDoubleLittleEndian(span, value);
_writer.Advance(8);
Position += 8;
}
/// <summary>
/// Writes a BSON binary field.
/// Writes a BSON binary field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="data">The binary data.</param>
public void WriteBinary(string name, ReadOnlySpan<byte> data)
{
WriteByte((byte)BsonType.Binary);
WriteCString(name);
WriteInt32Internal(data.Length);
WriteByte(0); // Binary subtype: Generic
WriteBytes(data);
{
WriteByte((byte)BsonType.Binary);
WriteCString(name);
WriteInt32Internal(data.Length);
WriteByte(0); // Binary subtype: Generic
WriteBytes(data);
}
/// <summary>
/// Writes a BSON 64-bit integer field.
/// Writes a BSON 64-bit integer field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The 64-bit integer value.</param>
public void WriteInt64(string name, long value)
{
WriteByte((byte)BsonType.Int64);
WriteCString(name);
WriteInt64Internal(value);
{
WriteByte((byte)BsonType.Int64);
WriteCString(name);
WriteInt64Internal(value);
}
/// <summary>
/// Writes a BSON double field.
/// Writes a BSON double field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The double value.</param>
public void WriteDouble(string name, double value)
{
WriteByte((byte)BsonType.Double);
WriteCString(name);
WriteDoubleInternal(value);
{
WriteByte((byte)BsonType.Double);
WriteCString(name);
WriteDoubleInternal(value);
}
private void WriteCString(string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value);
WriteBytes(bytes);
WriteByte(0); // Null terminator
}
private void WriteCString(string value)
{
var bytes = Encoding.UTF8.GetBytes(value);
WriteBytes(bytes);
WriteByte(0); // Null terminator
}
/// <summary>
/// Writes a BSON 32-bit integer field.
/// Writes a BSON 32-bit integer field.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The 32-bit integer value.</param>
public void WriteInt32(string name, int value)
{
WriteByte((byte)BsonType.Int32);
WriteCString(name);
WriteInt32Internal(value);
}
}
{
WriteByte((byte)BsonType.Int32);
WriteCString(name);
WriteInt32Internal(value);
}
}

View File

@@ -1,344 +1,343 @@
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text;
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// Zero-allocation BSON reader using ReadOnlySpan&lt;byte&gt;.
/// Implemented as ref struct to ensure stack-only allocation.
/// Zero-allocation BSON reader using ReadOnlySpan&lt;byte&gt;.
/// Implemented as ref struct to ensure stack-only allocation.
/// </summary>
public ref struct BsonSpanReader
{
private ReadOnlySpan<byte> _buffer;
private int _position;
private readonly System.Collections.Concurrent.ConcurrentDictionary<ushort, string> _keys;
/// <summary>
/// Initializes a new instance of the <see cref="BsonSpanReader"/> struct.
/// </summary>
/// <param name="buffer">The BSON buffer to read.</param>
/// <param name="keys">The reverse key dictionary used for compressed element headers.</param>
public BsonSpanReader(ReadOnlySpan<byte> buffer, System.Collections.Concurrent.ConcurrentDictionary<ushort, string> keys)
{
_buffer = buffer;
_position = 0;
_keys = keys;
}
/// <summary>
/// Gets the current read position in the buffer.
/// </summary>
public int Position => _position;
/// <summary>
/// Gets the number of unread bytes remaining in the buffer.
/// </summary>
public int Remaining => _buffer.Length - _position;
private readonly ConcurrentDictionary<ushort, string> _keys;
/// <summary>
/// Reads the document size (first 4 bytes of a BSON document)
/// Initializes a new instance of the <see cref="BsonSpanReader" /> struct.
/// </summary>
/// <param name="buffer">The BSON buffer to read.</param>
/// <param name="keys">The reverse key dictionary used for compressed element headers.</param>
public BsonSpanReader(ReadOnlySpan<byte> buffer, ConcurrentDictionary<ushort, string> keys)
{
_buffer = buffer;
Position = 0;
_keys = keys;
}
/// <summary>
/// Gets the current read position in the buffer.
/// </summary>
public int Position { get; private set; }
/// <summary>
/// Gets the number of unread bytes remaining in the buffer.
/// </summary>
public int Remaining => _buffer.Length - Position;
/// <summary>
/// Reads the document size (first 4 bytes of a BSON document)
/// </summary>
public int ReadDocumentSize()
{
if (Remaining < 4)
throw new InvalidOperationException("Not enough bytes to read document size");
var size = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4));
_position += 4;
int size = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position, 4));
Position += 4;
return size;
}
/// <summary>
/// Reads a BSON element type
/// Reads a BSON element type
/// </summary>
public BsonType ReadBsonType()
{
if (Remaining < 1)
throw new InvalidOperationException("Not enough bytes to read BSON type");
var type = (BsonType)_buffer[_position];
_position++;
var type = (BsonType)_buffer[Position];
Position++;
return type;
}
/// <summary>
/// Reads a C-style null-terminated string (e-name in BSON spec)
/// Reads a C-style null-terminated string (e-name in BSON spec)
/// </summary>
public string ReadCString()
{
var start = _position;
while (_position < _buffer.Length && _buffer[_position] != 0)
_position++;
int start = Position;
while (Position < _buffer.Length && _buffer[Position] != 0)
Position++;
if (_position >= _buffer.Length)
if (Position >= _buffer.Length)
throw new InvalidOperationException("Unterminated C-string");
var nameBytes = _buffer.Slice(start, _position - start);
_position++; // Skip null terminator
var nameBytes = _buffer.Slice(start, Position - start);
Position++; // Skip null terminator
return Encoding.UTF8.GetString(nameBytes);
}
/// <summary>
/// Reads a C-string into a destination span. Returns the number of bytes written.
/// </summary>
/// <param name="destination">The destination character span.</param>
public int ReadCString(Span<char> destination)
/// <summary>
/// Reads a C-string into a destination span. Returns the number of bytes written.
/// </summary>
/// <param name="destination">The destination character span.</param>
public int ReadCString(Span<char> destination)
{
var start = _position;
while (_position < _buffer.Length && _buffer[_position] != 0)
_position++;
int start = Position;
while (Position < _buffer.Length && _buffer[Position] != 0)
Position++;
if (_position >= _buffer.Length)
if (Position >= _buffer.Length)
throw new InvalidOperationException("Unterminated C-string");
var nameBytes = _buffer.Slice(start, _position - start);
_position++; // Skip null terminator
var nameBytes = _buffer.Slice(start, Position - start);
Position++; // Skip null terminator
return Encoding.UTF8.GetChars(nameBytes, destination);
}
/// <summary>
/// Reads a BSON string (4-byte length + UTF-8 bytes + null terminator)
/// Reads a BSON string (4-byte length + UTF-8 bytes + null terminator)
/// </summary>
public string ReadString()
{
var length = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4));
_position += 4;
int length = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position, 4));
Position += 4;
if (length < 1)
throw new InvalidOperationException("Invalid string length");
var stringBytes = _buffer.Slice(_position, length - 1); // Exclude null terminator
_position += length;
var stringBytes = _buffer.Slice(Position, length - 1); // Exclude null terminator
Position += length;
return Encoding.UTF8.GetString(stringBytes);
}
/// <summary>
/// Reads a 32-bit integer.
/// </summary>
public int ReadInt32()
/// <summary>
/// Reads a 32-bit integer.
/// </summary>
public int ReadInt32()
{
if (Remaining < 4)
throw new InvalidOperationException("Not enough bytes to read Int32");
var value = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4));
_position += 4;
return value;
}
/// <summary>
/// Reads a 64-bit integer.
/// </summary>
public long ReadInt64()
{
if (Remaining < 8)
throw new InvalidOperationException("Not enough bytes to read Int64");
var value = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8));
_position += 8;
return value;
}
/// <summary>
/// Reads a double-precision floating point value.
/// </summary>
public double ReadDouble()
{
if (Remaining < 8)
throw new InvalidOperationException("Not enough bytes to read Double");
var value = BinaryPrimitives.ReadDoubleLittleEndian(_buffer.Slice(_position, 8));
_position += 8;
int value = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position, 4));
Position += 4;
return value;
}
/// <summary>
/// Reads spatial coordinates from a BSON array [X, Y].
/// Returns a (double, double) tuple.
/// Reads a 64-bit integer.
/// </summary>
public long ReadInt64()
{
if (Remaining < 8)
throw new InvalidOperationException("Not enough bytes to read Int64");
long value = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(Position, 8));
Position += 8;
return value;
}
/// <summary>
/// Reads a double-precision floating point value.
/// </summary>
public double ReadDouble()
{
if (Remaining < 8)
throw new InvalidOperationException("Not enough bytes to read Double");
double value = BinaryPrimitives.ReadDoubleLittleEndian(_buffer.Slice(Position, 8));
Position += 8;
return value;
}
/// <summary>
/// Reads spatial coordinates from a BSON array [X, Y].
/// Returns a (double, double) tuple.
/// </summary>
public (double, double) ReadCoordinates()
{
// Skip array size (4 bytes)
_position += 4;
Position += 4;
// Skip element 0 header: Type(1) + Name("0\0") (3 bytes)
_position += 3;
var x = BinaryPrimitives.ReadDoubleLittleEndian(_buffer.Slice(_position, 8));
_position += 8;
Position += 3;
double x = BinaryPrimitives.ReadDoubleLittleEndian(_buffer.Slice(Position, 8));
Position += 8;
// Skip element 1 header: Type(1) + Name("1\0") (3 bytes)
_position += 3;
var y = BinaryPrimitives.ReadDoubleLittleEndian(_buffer.Slice(_position, 8));
_position += 8;
Position += 3;
double y = BinaryPrimitives.ReadDoubleLittleEndian(_buffer.Slice(Position, 8));
Position += 8;
// Skip end of array marker (1 byte)
_position++;
Position++;
return (x, y);
}
/// <summary>
/// Reads a Decimal128 value.
/// </summary>
public decimal ReadDecimal128()
/// <summary>
/// Reads a Decimal128 value.
/// </summary>
public decimal ReadDecimal128()
{
if (Remaining < 16)
throw new InvalidOperationException("Not enough bytes to read Decimal128");
var bits = new int[4];
bits[0] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4));
bits[1] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position + 4, 4));
bits[2] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position + 8, 4));
bits[3] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position + 12, 4));
_position += 16;
bits[0] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position, 4));
bits[1] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position + 4, 4));
bits[2] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position + 8, 4));
bits[3] = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position + 12, 4));
Position += 16;
return new decimal(bits);
}
/// <summary>
/// Reads a boolean value.
/// </summary>
public bool ReadBoolean()
/// <summary>
/// Reads a boolean value.
/// </summary>
public bool ReadBoolean()
{
if (Remaining < 1)
throw new InvalidOperationException("Not enough bytes to read Boolean");
var value = _buffer[_position] != 0;
_position++;
bool value = _buffer[Position] != 0;
Position++;
return value;
}
/// <summary>
/// Reads a BSON DateTime (UTC milliseconds since Unix epoch)
/// Reads a BSON DateTime (UTC milliseconds since Unix epoch)
/// </summary>
public DateTime ReadDateTime()
{
var milliseconds = ReadInt64();
long milliseconds = ReadInt64();
return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds).UtcDateTime;
}
/// <summary>
/// Reads a BSON DateTime as DateTimeOffset (UTC milliseconds since Unix epoch)
/// Reads a BSON DateTime as DateTimeOffset (UTC milliseconds since Unix epoch)
/// </summary>
public DateTimeOffset ReadDateTimeOffset()
{
var milliseconds = ReadInt64();
long milliseconds = ReadInt64();
return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds);
}
/// <summary>
/// Reads a TimeSpan from BSON Int64 (ticks)
/// Reads a TimeSpan from BSON Int64 (ticks)
/// </summary>
public TimeSpan ReadTimeSpan()
{
var ticks = ReadInt64();
long ticks = ReadInt64();
return TimeSpan.FromTicks(ticks);
}
/// <summary>
/// Reads a DateOnly from BSON Int32 (day number)
/// Reads a DateOnly from BSON Int32 (day number)
/// </summary>
public DateOnly ReadDateOnly()
{
var dayNumber = ReadInt32();
int dayNumber = ReadInt32();
return DateOnly.FromDayNumber(dayNumber);
}
/// <summary>
/// Reads a TimeOnly from BSON Int64 (ticks)
/// Reads a TimeOnly from BSON Int64 (ticks)
/// </summary>
public TimeOnly ReadTimeOnly()
{
var ticks = ReadInt64();
long ticks = ReadInt64();
return new TimeOnly(ticks);
}
/// <summary>
/// Reads a GUID value.
/// </summary>
public Guid ReadGuid()
/// <summary>
/// Reads a GUID value.
/// </summary>
public Guid ReadGuid()
{
return Guid.Parse(ReadString());
}
/// <summary>
/// Reads a BSON ObjectId (12 bytes)
/// Reads a BSON ObjectId (12 bytes)
/// </summary>
public ObjectId ReadObjectId()
{
if (Remaining < 12)
throw new InvalidOperationException("Not enough bytes to read ObjectId");
var oidBytes = _buffer.Slice(_position, 12);
_position += 12;
var oidBytes = _buffer.Slice(Position, 12);
Position += 12;
return new ObjectId(oidBytes);
}
/// <summary>
/// Reads binary data (subtype + length + bytes)
/// </summary>
/// <param name="subtype">When this method returns, contains the BSON binary subtype.</param>
public ReadOnlySpan<byte> ReadBinary(out byte subtype)
/// <summary>
/// Reads binary data (subtype + length + bytes)
/// </summary>
/// <param name="subtype">When this method returns, contains the BSON binary subtype.</param>
public ReadOnlySpan<byte> ReadBinary(out byte subtype)
{
var length = ReadInt32();
int length = ReadInt32();
if (Remaining < 1)
throw new InvalidOperationException("Not enough bytes to read binary subtype");
subtype = _buffer[_position];
_position++;
throw new InvalidOperationException("Not enough bytes to read binary subtype");
subtype = _buffer[Position];
Position++;
if (Remaining < length)
throw new InvalidOperationException("Not enough bytes to read binary data");
var data = _buffer.Slice(_position, length);
_position += length;
var data = _buffer.Slice(Position, length);
Position += length;
return data;
}
/// <summary>
/// Skips the current value based on type
/// </summary>
/// <param name="type">The BSON type of the value to skip.</param>
public void SkipValue(BsonType type)
/// <summary>
/// Skips the current value based on type
/// </summary>
/// <param name="type">The BSON type of the value to skip.</param>
public void SkipValue(BsonType type)
{
switch (type)
{
case BsonType.Double:
_position += 8;
Position += 8;
break;
case BsonType.String:
var stringLength = ReadInt32();
_position += stringLength;
int stringLength = ReadInt32();
Position += stringLength;
break;
case BsonType.Document:
case BsonType.Array:
var docLength = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4));
_position += docLength;
int docLength = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position, 4));
Position += docLength;
break;
case BsonType.Binary:
var binaryLength = ReadInt32();
_position += 1 + binaryLength; // subtype + data
int binaryLength = ReadInt32();
Position += 1 + binaryLength; // subtype + data
break;
case BsonType.ObjectId:
_position += 12;
Position += 12;
break;
case BsonType.Boolean:
_position += 1;
Position += 1;
break;
case BsonType.DateTime:
case BsonType.Int64:
case BsonType.Timestamp:
_position += 8;
Position += 8;
break;
case BsonType.Decimal128:
_position += 16;
Position += 16;
break;
case BsonType.Int32:
_position += 4;
Position += 4;
break;
case BsonType.Null:
// No data
@@ -348,49 +347,50 @@ public ref struct BsonSpanReader
}
}
/// <summary>
/// Reads a single byte.
/// </summary>
public byte ReadByte()
/// <summary>
/// Reads a single byte.
/// </summary>
public byte ReadByte()
{
if (Remaining < 1)
throw new InvalidOperationException("Not enough bytes to read byte");
var value = _buffer[_position];
_position++;
byte value = _buffer[Position];
Position++;
return value;
}
/// <summary>
/// Peeks a 32-bit integer at the current position without advancing.
/// </summary>
public int PeekInt32()
/// <summary>
/// Peeks a 32-bit integer at the current position without advancing.
/// </summary>
public int PeekInt32()
{
if (Remaining < 4)
throw new InvalidOperationException("Not enough bytes to peek Int32");
return BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4));
return BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(Position, 4));
}
/// <summary>
/// Reads an element header key identifier and resolves it to a key name.
/// </summary>
public string ReadElementHeader()
/// <summary>
/// Reads an element header key identifier and resolves it to a key name.
/// </summary>
public string ReadElementHeader()
{
if (Remaining < 2)
throw new InvalidOperationException("Not enough bytes to read BSON element key ID");
var id = BinaryPrimitives.ReadUInt16LittleEndian(_buffer.Slice(_position, 2));
_position += 2;
ushort id = BinaryPrimitives.ReadUInt16LittleEndian(_buffer.Slice(Position, 2));
Position += 2;
if (!_keys.TryGetValue(id, out var key))
{
if (!_keys.TryGetValue(id, out string? key))
throw new InvalidOperationException($"BSON Key ID {id} not found in reverse key dictionary.");
}
return key;
}
/// <summary>
/// Returns a span containing all unread bytes.
/// </summary>
public ReadOnlySpan<byte> RemainingBytes() => _buffer[_position..];
}
/// <summary>
/// Returns a span containing all unread bytes.
/// </summary>
public ReadOnlySpan<byte> RemainingBytes()
{
return _buffer[Position..];
}
}

View File

@@ -1,382 +1,380 @@
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text;
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// Zero-allocation BSON writer using Span&lt;byte&gt;.
/// Implemented as ref struct to ensure stack-only allocation.
/// Zero-allocation BSON writer using Span&lt;byte&gt;.
/// Implemented as ref struct to ensure stack-only allocation.
/// </summary>
public ref struct BsonSpanWriter
{
private Span<byte> _buffer;
private int _position;
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, ushort> _keyMap;
private readonly ConcurrentDictionary<string, ushort> _keyMap;
/// <summary>
/// Initializes a new instance of the <see cref="BsonSpanWriter"/> struct.
/// </summary>
/// <param name="buffer">The destination buffer to write BSON bytes into.</param>
/// <param name="keyMap">The cached key-name to key-id mapping.</param>
public BsonSpanWriter(Span<byte> buffer, System.Collections.Concurrent.ConcurrentDictionary<string, ushort> keyMap)
/// <summary>
/// Initializes a new instance of the <see cref="BsonSpanWriter" /> struct.
/// </summary>
/// <param name="buffer">The destination buffer to write BSON bytes into.</param>
/// <param name="keyMap">The cached key-name to key-id mapping.</param>
public BsonSpanWriter(Span<byte> buffer, ConcurrentDictionary<string, ushort> keyMap)
{
_buffer = buffer;
_keyMap = keyMap;
_position = 0;
Position = 0;
}
/// <summary>
/// Gets the current write position in the buffer.
/// </summary>
public int Position => _position;
/// <summary>
/// Gets the number of bytes remaining in the buffer.
/// </summary>
public int Remaining => _buffer.Length - _position;
/// <summary>
/// Gets the current write position in the buffer.
/// </summary>
public int Position { get; private set; }
/// <summary>
/// Writes document size placeholder and returns the position to patch later
/// Gets the number of bytes remaining in the buffer.
/// </summary>
public int Remaining => _buffer.Length - Position;
/// <summary>
/// Writes document size placeholder and returns the position to patch later
/// </summary>
public int WriteDocumentSizePlaceholder()
{
var sizePosition = _position;
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position, 4), 0);
_position += 4;
int sizePosition = Position;
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position, 4), 0);
Position += 4;
return sizePosition;
}
/// <summary>
/// Patches the document size at the given position
/// </summary>
/// <param name="sizePosition">The position where the size placeholder was written.</param>
public void PatchDocumentSize(int sizePosition)
/// <summary>
/// Patches the document size at the given position
/// </summary>
/// <param name="sizePosition">The position where the size placeholder was written.</param>
public void PatchDocumentSize(int sizePosition)
{
var size = _position - sizePosition;
int size = Position - sizePosition;
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(sizePosition, 4), size);
}
/// <summary>
/// Writes a BSON element header (type + name)
/// </summary>
/// <param name="type">The BSON element type.</param>
/// <param name="name">The field name.</param>
public void WriteElementHeader(BsonType type, string name)
/// <summary>
/// Writes a BSON element header (type + name)
/// </summary>
/// <param name="type">The BSON element type.</param>
/// <param name="name">The field name.</param>
public void WriteElementHeader(BsonType type, string name)
{
_buffer[_position] = (byte)type;
_position++;
_buffer[Position] = (byte)type;
Position++;
if (!_keyMap.TryGetValue(name, out var id))
{
throw new InvalidOperationException($"BSON Key '{name}' not found in dictionary cache. Ensure all keys are registered before serialization.");
}
if (!_keyMap.TryGetValue(name, out ushort id))
throw new InvalidOperationException(
$"BSON Key '{name}' not found in dictionary cache. Ensure all keys are registered before serialization.");
BinaryPrimitives.WriteUInt16LittleEndian(_buffer.Slice(_position, 2), id);
_position += 2;
BinaryPrimitives.WriteUInt16LittleEndian(_buffer.Slice(Position, 2), id);
Position += 2;
}
/// <summary>
/// Writes a C-style null-terminated string
/// Writes a C-style null-terminated string
/// </summary>
private void WriteCString(string value)
{
var bytesWritten = Encoding.UTF8.GetBytes(value, _buffer[_position..]);
_position += bytesWritten;
_buffer[_position] = 0; // Null terminator
_position++;
int bytesWritten = Encoding.UTF8.GetBytes(value, _buffer[Position..]);
Position += bytesWritten;
_buffer[Position] = 0; // Null terminator
Position++;
}
/// <summary>
/// Writes end-of-document marker
/// Writes end-of-document marker
/// </summary>
public void WriteEndOfDocument()
{
_buffer[_position] = 0;
_position++;
_buffer[Position] = 0;
Position++;
}
/// <summary>
/// Writes a BSON string element
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The string value.</param>
public void WriteString(string name, string value)
/// <summary>
/// Writes a BSON string element
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The string value.</param>
public void WriteString(string name, string value)
{
WriteElementHeader(BsonType.String, name);
var valueBytes = Encoding.UTF8.GetByteCount(value);
var stringLength = valueBytes + 1; // Include null terminator
int valueBytes = Encoding.UTF8.GetByteCount(value);
int stringLength = valueBytes + 1; // Include null terminator
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position, 4), stringLength);
_position += 4;
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position, 4), stringLength);
Position += 4;
Encoding.UTF8.GetBytes(value, _buffer[_position..]);
_position += valueBytes;
Encoding.UTF8.GetBytes(value, _buffer[Position..]);
Position += valueBytes;
_buffer[_position] = 0; // Null terminator
_position++;
_buffer[Position] = 0; // Null terminator
Position++;
}
/// <summary>
/// Writes a BSON int32 element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The 32-bit integer value.</param>
public void WriteInt32(string name, int value)
/// <summary>
/// Writes a BSON int32 element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The 32-bit integer value.</param>
public void WriteInt32(string name, int value)
{
WriteElementHeader(BsonType.Int32, name);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position, 4), value);
_position += 4;
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position, 4), value);
Position += 4;
}
/// <summary>
/// Writes a BSON int64 element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The 64-bit integer value.</param>
public void WriteInt64(string name, long value)
/// <summary>
/// Writes a BSON int64 element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The 64-bit integer value.</param>
public void WriteInt64(string name, long value)
{
WriteElementHeader(BsonType.Int64, name);
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(_position, 8), value);
_position += 8;
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(Position, 8), value);
Position += 8;
}
/// <summary>
/// Writes a BSON double element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The double-precision value.</param>
public void WriteDouble(string name, double value)
/// <summary>
/// Writes a BSON double element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The double-precision value.</param>
public void WriteDouble(string name, double value)
{
WriteElementHeader(BsonType.Double, name);
BinaryPrimitives.WriteDoubleLittleEndian(_buffer.Slice(_position, 8), value);
_position += 8;
BinaryPrimitives.WriteDoubleLittleEndian(_buffer.Slice(Position, 8), value);
Position += 8;
}
/// <summary>
/// Writes spatial coordinates as a BSON array [X, Y].
/// Optimized for (double, double) tuples.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="coordinates">The coordinate tuple as (X, Y).</param>
public void WriteCoordinates(string name, (double, double) coordinates)
/// <summary>
/// Writes spatial coordinates as a BSON array [X, Y].
/// Optimized for (double, double) tuples.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="coordinates">The coordinate tuple as (X, Y).</param>
public void WriteCoordinates(string name, (double, double) coordinates)
{
WriteElementHeader(BsonType.Array, name);
var startPos = _position;
_position += 4; // Placeholder for array size
WriteElementHeader(BsonType.Array, name);
int startPos = Position;
Position += 4; // Placeholder for array size
// Element 0: X
_buffer[_position++] = (byte)BsonType.Double;
_buffer[_position++] = 0x30; // '0'
_buffer[_position++] = 0x00; // Null
BinaryPrimitives.WriteDoubleLittleEndian(_buffer.Slice(_position, 8), coordinates.Item1);
_position += 8;
_buffer[Position++] = (byte)BsonType.Double;
_buffer[Position++] = 0x30; // '0'
_buffer[Position++] = 0x00; // Null
BinaryPrimitives.WriteDoubleLittleEndian(_buffer.Slice(Position, 8), coordinates.Item1);
Position += 8;
// Element 1: Y
_buffer[_position++] = (byte)BsonType.Double;
_buffer[_position++] = 0x31; // '1'
_buffer[_position++] = 0x00; // Null
BinaryPrimitives.WriteDoubleLittleEndian(_buffer.Slice(_position, 8), coordinates.Item2);
_position += 8;
_buffer[Position++] = (byte)BsonType.Double;
_buffer[Position++] = 0x31; // '1'
_buffer[Position++] = 0x00; // Null
BinaryPrimitives.WriteDoubleLittleEndian(_buffer.Slice(Position, 8), coordinates.Item2);
Position += 8;
_buffer[_position++] = 0x00; // End of array marker
_buffer[Position++] = 0x00; // End of array marker
// Patch array size
var size = _position - startPos;
int size = Position - startPos;
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(startPos, 4), size);
}
/// <summary>
/// Writes a BSON Decimal128 element from a <see cref="decimal"/> value.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The decimal value.</param>
public void WriteDecimal128(string name, decimal value)
/// <summary>
/// Writes a BSON Decimal128 element from a <see cref="decimal" /> value.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The decimal value.</param>
public void WriteDecimal128(string name, decimal value)
{
WriteElementHeader(BsonType.Decimal128, name);
// Note: usage of C# decimal bits for round-trip fidelity within ZB.MOM.WW.CBDD.
// This makes it compatible with CBDD Reader but strictly speaking not standard IEEE 754-2008 Decimal128.
var bits = decimal.GetBits(value);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position, 4), bits[0]);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position + 4, 4), bits[1]);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position + 8, 4), bits[2]);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position + 12, 4), bits[3]);
_position += 16;
int[] bits = decimal.GetBits(value);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position, 4), bits[0]);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position + 4, 4), bits[1]);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position + 8, 4), bits[2]);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position + 12, 4), bits[3]);
Position += 16;
}
/// <summary>
/// Writes a BSON boolean element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The boolean value.</param>
public void WriteBoolean(string name, bool value)
/// <summary>
/// Writes a BSON boolean element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The boolean value.</param>
public void WriteBoolean(string name, bool value)
{
WriteElementHeader(BsonType.Boolean, name);
_buffer[_position] = (byte)(value ? 1 : 0);
_position++;
_buffer[Position] = (byte)(value ? 1 : 0);
Position++;
}
/// <summary>
/// Writes a BSON UTC datetime element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The date and time value.</param>
public void WriteDateTime(string name, DateTime value)
/// <summary>
/// Writes a BSON UTC datetime element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The date and time value.</param>
public void WriteDateTime(string name, DateTime value)
{
WriteElementHeader(BsonType.DateTime, name);
var milliseconds = new DateTimeOffset(value.ToUniversalTime()).ToUnixTimeMilliseconds();
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(_position, 8), milliseconds);
_position += 8;
long milliseconds = new DateTimeOffset(value.ToUniversalTime()).ToUnixTimeMilliseconds();
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(Position, 8), milliseconds);
Position += 8;
}
/// <summary>
/// Writes a BSON UTC datetime element from a <see cref="DateTimeOffset"/> value.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The date and time offset value.</param>
public void WriteDateTimeOffset(string name, DateTimeOffset value)
/// <summary>
/// Writes a BSON UTC datetime element from a <see cref="DateTimeOffset" /> value.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The date and time offset value.</param>
public void WriteDateTimeOffset(string name, DateTimeOffset value)
{
WriteElementHeader(BsonType.DateTime, name);
var milliseconds = value.ToUnixTimeMilliseconds();
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(_position, 8), milliseconds);
_position += 8;
long milliseconds = value.ToUnixTimeMilliseconds();
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(Position, 8), milliseconds);
Position += 8;
}
/// <summary>
/// Writes a BSON int64 element containing ticks from a <see cref="TimeSpan"/>.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The time span value.</param>
public void WriteTimeSpan(string name, TimeSpan value)
/// <summary>
/// Writes a BSON int64 element containing ticks from a <see cref="TimeSpan" />.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The time span value.</param>
public void WriteTimeSpan(string name, TimeSpan value)
{
WriteElementHeader(BsonType.Int64, name);
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(_position, 8), value.Ticks);
_position += 8;
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(Position, 8), value.Ticks);
Position += 8;
}
/// <summary>
/// Writes a BSON int32 element containing the <see cref="DateOnly.DayNumber"/>.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The date-only value.</param>
public void WriteDateOnly(string name, DateOnly value)
/// <summary>
/// Writes a BSON int32 element containing the <see cref="DateOnly.DayNumber" />.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The date-only value.</param>
public void WriteDateOnly(string name, DateOnly value)
{
WriteElementHeader(BsonType.Int32, name);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position, 4), value.DayNumber);
_position += 4;
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position, 4), value.DayNumber);
Position += 4;
}
/// <summary>
/// Writes a BSON int64 element containing ticks from a <see cref="TimeOnly"/>.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The time-only value.</param>
public void WriteTimeOnly(string name, TimeOnly value)
/// <summary>
/// Writes a BSON int64 element containing ticks from a <see cref="TimeOnly" />.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The time-only value.</param>
public void WriteTimeOnly(string name, TimeOnly value)
{
WriteElementHeader(BsonType.Int64, name);
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(_position, 8), value.Ticks);
_position += 8;
BinaryPrimitives.WriteInt64LittleEndian(_buffer.Slice(Position, 8), value.Ticks);
Position += 8;
}
/// <summary>
/// Writes a GUID as a BSON string element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The GUID value.</param>
public void WriteGuid(string name, Guid value)
/// <summary>
/// Writes a GUID as a BSON string element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The GUID value.</param>
public void WriteGuid(string name, Guid value)
{
WriteString(name, value.ToString());
}
/// <summary>
/// Writes a BSON ObjectId element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The ObjectId value.</param>
public void WriteObjectId(string name, ObjectId value)
/// <summary>
/// Writes a BSON ObjectId element.
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="value">The ObjectId value.</param>
public void WriteObjectId(string name, ObjectId value)
{
WriteElementHeader(BsonType.ObjectId, name);
value.WriteTo(_buffer.Slice(_position, 12));
_position += 12;
value.WriteTo(_buffer.Slice(Position, 12));
Position += 12;
}
/// <summary>
/// Writes a BSON null element.
/// </summary>
/// <param name="name">The field name.</param>
public void WriteNull(string name)
/// <summary>
/// Writes a BSON null element.
/// </summary>
/// <param name="name">The field name.</param>
public void WriteNull(string name)
{
WriteElementHeader(BsonType.Null, name);
// No value to write for null
}
/// <summary>
/// Writes binary data
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="data">The binary payload.</param>
/// <param name="subtype">The BSON binary subtype.</param>
public void WriteBinary(string name, ReadOnlySpan<byte> data, byte subtype = 0)
/// <summary>
/// Writes binary data
/// </summary>
/// <param name="name">The field name.</param>
/// <param name="data">The binary payload.</param>
/// <param name="subtype">The BSON binary subtype.</param>
public void WriteBinary(string name, ReadOnlySpan<byte> data, byte subtype = 0)
{
WriteElementHeader(BsonType.Binary, name);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position, 4), data.Length);
_position += 4;
_buffer[_position] = subtype;
_position++;
data.CopyTo(_buffer[_position..]);
_position += data.Length;
WriteElementHeader(BsonType.Binary, name);
BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(Position, 4), data.Length);
Position += 4;
_buffer[Position] = subtype;
Position++;
data.CopyTo(_buffer[Position..]);
Position += data.Length;
}
/// <summary>
/// Begins writing a subdocument and returns the size position to patch later
/// </summary>
/// <param name="name">The field name for the subdocument.</param>
public int BeginDocument(string name)
/// <summary>
/// Begins writing a subdocument and returns the size position to patch later
/// </summary>
/// <param name="name">The field name for the subdocument.</param>
public int BeginDocument(string name)
{
WriteElementHeader(BsonType.Document, name);
return WriteDocumentSizePlaceholder();
}
/// <summary>
/// Begins writing the root document and returns the size position to patch later
/// Begins writing the root document and returns the size position to patch later
/// </summary>
public int BeginDocument()
{
return WriteDocumentSizePlaceholder();
}
/// <summary>
/// Ends the current document
/// </summary>
/// <param name="sizePosition">The position returned by <see cref="WriteDocumentSizePlaceholder"/>.</param>
public void EndDocument(int sizePosition)
/// <summary>
/// Ends the current document
/// </summary>
/// <param name="sizePosition">The position returned by <see cref="WriteDocumentSizePlaceholder" />.</param>
public void EndDocument(int sizePosition)
{
WriteEndOfDocument();
PatchDocumentSize(sizePosition);
}
/// <summary>
/// Begins writing a BSON array and returns the size position to patch later
/// </summary>
/// <param name="name">The field name for the array.</param>
public int BeginArray(string name)
/// <summary>
/// Begins writing a BSON array and returns the size position to patch later
/// </summary>
/// <param name="name">The field name for the array.</param>
public int BeginArray(string name)
{
WriteElementHeader(BsonType.Array, name);
return WriteDocumentSizePlaceholder();
}
/// <summary>
/// Ends the current BSON array
/// </summary>
/// <param name="sizePosition">The position returned by <see cref="WriteDocumentSizePlaceholder"/>.</param>
public void EndArray(int sizePosition)
/// <summary>
/// Ends the current BSON array
/// </summary>
/// <param name="sizePosition">The position returned by <see cref="WriteDocumentSizePlaceholder" />.</param>
public void EndArray(int sizePosition)
{
WriteEndOfDocument();
PatchDocumentSize(sizePosition);
}
}
}

View File

@@ -1,14 +1,11 @@
using System;
namespace ZB.MOM.WW.CBDD.Bson;
namespace ZB.MOM.WW.CBDD.Bson
[AttributeUsage(AttributeTargets.Property)]
public class BsonIdAttribute : Attribute
{
[AttributeUsage(AttributeTargets.Property)]
public class BsonIdAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Property)]
public class BsonIgnoreAttribute : Attribute
{
}
}
[AttributeUsage(AttributeTargets.Property)]
public class BsonIgnoreAttribute : Attribute
{
}

View File

@@ -1,59 +1,56 @@
namespace ZB.MOM.WW.CBDD.Bson.Schema;
public partial class BsonField
namespace ZB.MOM.WW.CBDD.Bson.Schema;
public class BsonField
{
/// <summary>
/// Gets the field name.
/// Gets the field name.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Gets the field BSON type.
/// Gets the field BSON type.
/// </summary>
public BsonType Type { get; init; }
/// <summary>
/// Gets a value indicating whether the field is nullable.
/// Gets a value indicating whether the field is nullable.
/// </summary>
public bool IsNullable { get; init; }
/// <summary>
/// Gets the nested schema when this field is a document.
/// Gets the nested schema when this field is a document.
/// </summary>
public BsonSchema? NestedSchema { get; init; }
/// <summary>
/// Gets the array item type when this field is an array.
/// Gets the array item type when this field is an array.
/// </summary>
public BsonType? ArrayItemType { get; init; }
/// <summary>
/// Writes this field definition to BSON.
/// Writes this field definition to BSON.
/// </summary>
/// <param name="writer">The BSON writer.</param>
public void ToBson(ref BsonSpanWriter writer)
{
var size = writer.BeginDocument();
writer.WriteString("n", Name);
writer.WriteInt32("t", (int)Type);
int size = writer.BeginDocument();
writer.WriteString("n", Name);
writer.WriteInt32("t", (int)Type);
writer.WriteBoolean("b", IsNullable);
if (NestedSchema != null)
{
writer.WriteElementHeader(BsonType.Document, "s");
NestedSchema.ToBson(ref writer);
if (NestedSchema != null)
{
writer.WriteElementHeader(BsonType.Document, "s");
NestedSchema.ToBson(ref writer);
}
if (ArrayItemType != null)
{
writer.WriteInt32("a", (int)ArrayItemType.Value);
}
if (ArrayItemType != null) writer.WriteInt32("a", (int)ArrayItemType.Value);
writer.EndDocument(size);
}
/// <summary>
/// Reads a field definition from BSON.
/// Reads a field definition from BSON.
/// </summary>
/// <param name="reader">The BSON reader.</param>
/// <returns>The deserialized field.</returns>
@@ -61,59 +58,59 @@ public partial class BsonField
{
reader.ReadInt32(); // Read doc size
string name = "";
BsonType type = BsonType.Null;
bool isNullable = false;
BsonSchema? nestedSchema = null;
BsonType? arrayItemType = null;
while (reader.Remaining > 1)
{
var btype = reader.ReadBsonType();
var name = "";
var type = BsonType.Null;
var isNullable = false;
BsonSchema? nestedSchema = null;
BsonType? arrayItemType = null;
while (reader.Remaining > 1)
{
var btype = reader.ReadBsonType();
if (btype == BsonType.EndOfDocument) break;
var key = reader.ReadElementHeader();
switch (key)
{
case "n": name = reader.ReadString(); break;
case "t": type = (BsonType)reader.ReadInt32(); break;
case "b": isNullable = reader.ReadBoolean(); break;
case "s": nestedSchema = BsonSchema.FromBson(ref reader); break;
case "a": arrayItemType = (BsonType)reader.ReadInt32(); break;
default: reader.SkipValue(btype); break;
}
string key = reader.ReadElementHeader();
switch (key)
{
case "n": name = reader.ReadString(); break;
case "t": type = (BsonType)reader.ReadInt32(); break;
case "b": isNullable = reader.ReadBoolean(); break;
case "s": nestedSchema = BsonSchema.FromBson(ref reader); break;
case "a": arrayItemType = (BsonType)reader.ReadInt32(); break;
default: reader.SkipValue(btype); break;
}
}
return new BsonField
{
Name = name,
Type = type,
IsNullable = isNullable,
NestedSchema = nestedSchema,
return new BsonField
{
Name = name,
Type = type,
IsNullable = isNullable,
NestedSchema = nestedSchema,
ArrayItemType = arrayItemType
};
}
/// <summary>
/// Computes a hash representing the field definition.
/// Computes a hash representing the field definition.
/// </summary>
/// <returns>The computed hash value.</returns>
public long GetHash()
{
var hash = new HashCode();
hash.Add(Name);
hash.Add((int)Type);
hash.Add(IsNullable);
hash.Add(ArrayItemType);
hash.Add(Name);
hash.Add((int)Type);
hash.Add(IsNullable);
hash.Add(ArrayItemType);
if (NestedSchema != null) hash.Add(NestedSchema.GetHash());
return hash.ToHashCode();
}
/// <summary>
/// Determines whether this field is equal to another field.
/// Determines whether this field is equal to another field.
/// </summary>
/// <param name="other">The other field.</param>
/// <returns><see langword="true"/> if the fields are equal; otherwise, <see langword="false"/>.</returns>
/// <returns><see langword="true" /> if the fields are equal; otherwise, <see langword="false" />.</returns>
public bool Equals(BsonField? other)
{
if (other == null) return false;
@@ -121,8 +118,14 @@ public partial class BsonField
}
/// <inheritdoc />
public override bool Equals(object? obj) => Equals(obj as BsonField);
public override bool Equals(object? obj)
{
return Equals(obj as BsonField);
}
/// <inheritdoc />
public override int GetHashCode() => (int)GetHash();
}
public override int GetHashCode()
{
return (int)GetHash();
}
}

View File

@@ -1,45 +1,46 @@
namespace ZB.MOM.WW.CBDD.Bson.Schema;
public partial class BsonSchema
namespace ZB.MOM.WW.CBDD.Bson.Schema;
public class BsonSchema
{
/// <summary>
/// Gets or sets the schema title.
/// Gets or sets the schema title.
/// </summary>
public string? Title { get; set; }
/// <summary>
/// Gets or sets the schema version.
/// Gets or sets the schema version.
/// </summary>
public int? Version { get; set; }
/// <summary>
/// Gets the schema fields.
/// Gets the schema fields.
/// </summary>
public List<BsonField> Fields { get; } = new();
/// <summary>
/// Serializes this schema instance to BSON.
/// Serializes this schema instance to BSON.
/// </summary>
/// <param name="writer">The BSON writer to write into.</param>
public void ToBson(ref BsonSpanWriter writer)
{
var size = writer.BeginDocument();
if (Title != null) writer.WriteString("t", Title);
int size = writer.BeginDocument();
if (Title != null) writer.WriteString("t", Title);
if (Version != null) writer.WriteInt32("_v", Version.Value);
var fieldsSize = writer.BeginArray("f");
for (int i = 0; i < Fields.Count; i++)
{
writer.WriteElementHeader(BsonType.Document, i.ToString());
Fields[i].ToBson(ref writer);
}
int fieldsSize = writer.BeginArray("f");
for (var i = 0; i < Fields.Count; i++)
{
writer.WriteElementHeader(BsonType.Document, i.ToString());
Fields[i].ToBson(ref writer);
}
writer.EndArray(fieldsSize);
writer.EndDocument(size);
}
/// <summary>
/// Deserializes a schema instance from BSON.
/// Deserializes a schema instance from BSON.
/// </summary>
/// <param name="reader">The BSON reader to read from.</param>
/// <returns>The deserialized schema.</returns>
@@ -47,55 +48,53 @@ public partial class BsonSchema
{
reader.ReadInt32(); // Read doc size
var schema = new BsonSchema();
while (reader.Remaining > 1)
{
var btype = reader.ReadBsonType();
var schema = new BsonSchema();
while (reader.Remaining > 1)
{
var btype = reader.ReadBsonType();
if (btype == BsonType.EndOfDocument) break;
var key = reader.ReadElementHeader();
switch (key)
{
case "t": schema.Title = reader.ReadString(); break;
case "_v": schema.Version = reader.ReadInt32(); break;
case "f":
reader.ReadInt32(); // array size
while (reader.Remaining > 1)
{
var itemType = reader.ReadBsonType();
if (itemType == BsonType.EndOfDocument) break;
reader.ReadElementHeader(); // index
schema.Fields.Add(BsonField.FromBson(ref reader));
}
break;
default: reader.SkipValue(btype); break;
}
string key = reader.ReadElementHeader();
switch (key)
{
case "t": schema.Title = reader.ReadString(); break;
case "_v": schema.Version = reader.ReadInt32(); break;
case "f":
reader.ReadInt32(); // array size
while (reader.Remaining > 1)
{
var itemType = reader.ReadBsonType();
if (itemType == BsonType.EndOfDocument) break;
reader.ReadElementHeader(); // index
schema.Fields.Add(BsonField.FromBson(ref reader));
}
break;
default: reader.SkipValue(btype); break;
}
}
return schema;
}
/// <summary>
/// Computes a hash value for this schema based on its contents.
/// Computes a hash value for this schema based on its contents.
/// </summary>
/// <returns>The computed hash value.</returns>
public long GetHash()
{
var hash = new HashCode();
hash.Add(Title);
foreach (var field in Fields)
{
hash.Add(field.GetHash());
}
var hash = new HashCode();
hash.Add(Title);
foreach (var field in Fields) hash.Add(field.GetHash());
return hash.ToHashCode();
}
/// <summary>
/// Determines whether this schema is equal to another schema.
/// Determines whether this schema is equal to another schema.
/// </summary>
/// <param name="other">The schema to compare with.</param>
/// <returns><see langword="true"/> when schemas are equal; otherwise, <see langword="false"/>.</returns>
/// <returns><see langword="true" /> when schemas are equal; otherwise, <see langword="false" />.</returns>
public bool Equals(BsonSchema? other)
{
if (other == null) return false;
@@ -103,27 +102,29 @@ public partial class BsonSchema
}
/// <inheritdoc />
public override bool Equals(object? obj) => Equals(obj as BsonSchema);
public override bool Equals(object? obj)
{
return Equals(obj as BsonSchema);
}
/// <inheritdoc />
public override int GetHashCode() => (int)GetHash();
public override int GetHashCode()
{
return (int)GetHash();
}
/// <summary>
/// Enumerates all field keys in this schema, including nested schema keys.
/// Enumerates all field keys in this schema, including nested schema keys.
/// </summary>
/// <returns>An enumerable of field keys.</returns>
public IEnumerable<string> GetAllKeys()
{
foreach (var field in Fields)
{
yield return field.Name;
if (field.NestedSchema != null)
{
foreach (var nestedKey in field.NestedSchema.GetAllKeys())
{
yield return nestedKey;
}
}
}
}
}
foreach (var field in Fields)
{
yield return field.Name;
if (field.NestedSchema != null)
foreach (string nestedKey in field.NestedSchema.GetAllKeys())
yield return nestedKey;
}
}
}

View File

@@ -1,11 +1,10 @@
using System;
using System.Runtime.InteropServices;
namespace ZB.MOM.WW.CBDD.Bson;
/// <summary>
/// 12-byte ObjectId compatible with MongoDB ObjectId.
/// Implemented as readonly struct for zero allocation.
/// 12-byte ObjectId compatible with MongoDB ObjectId.
/// Implemented as readonly struct for zero allocation.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 12)]
public readonly struct ObjectId : IEquatable<ObjectId>
@@ -14,20 +13,20 @@ public readonly struct ObjectId : IEquatable<ObjectId>
[FieldOffset(4)] private readonly long _randomAndCounter;
/// <summary>
/// Empty ObjectId (all zeros)
/// Empty ObjectId (all zeros)
/// </summary>
public static readonly ObjectId Empty = new ObjectId(0, 0);
public static readonly ObjectId Empty = new(0, 0);
/// <summary>
/// Maximum ObjectId (all 0xFF bytes) - useful for range queries
/// Maximum ObjectId (all 0xFF bytes) - useful for range queries
/// </summary>
public static readonly ObjectId MaxValue = new ObjectId(int.MaxValue, long.MaxValue);
public static readonly ObjectId MaxValue = new(int.MaxValue, long.MaxValue);
/// <summary>
/// Initializes a new instance of the <see cref="ObjectId"/> struct from raw bytes.
/// </summary>
/// <param name="bytes">The 12-byte ObjectId value.</param>
public ObjectId(ReadOnlySpan<byte> bytes)
/// <summary>
/// Initializes a new instance of the <see cref="ObjectId" /> struct from raw bytes.
/// </summary>
/// <param name="bytes">The 12-byte ObjectId value.</param>
public ObjectId(ReadOnlySpan<byte> bytes)
{
if (bytes.Length != 12)
throw new ArgumentException("ObjectId must be exactly 12 bytes", nameof(bytes));
@@ -36,32 +35,32 @@ public readonly struct ObjectId : IEquatable<ObjectId>
_randomAndCounter = BitConverter.ToInt64(bytes[4..12]);
}
/// <summary>
/// Initializes a new instance of the <see cref="ObjectId"/> struct from its components.
/// </summary>
/// <param name="timestamp">The Unix timestamp portion.</param>
/// <param name="randomAndCounter">The random and counter portion.</param>
public ObjectId(int timestamp, long randomAndCounter)
/// <summary>
/// Initializes a new instance of the <see cref="ObjectId" /> struct from its components.
/// </summary>
/// <param name="timestamp">The Unix timestamp portion.</param>
/// <param name="randomAndCounter">The random and counter portion.</param>
public ObjectId(int timestamp, long randomAndCounter)
{
_timestamp = timestamp;
_randomAndCounter = randomAndCounter;
}
/// <summary>
/// Creates a new ObjectId with current timestamp
/// Creates a new ObjectId with current timestamp
/// </summary>
public static ObjectId NewObjectId()
{
var timestamp = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var random = Random.Shared.NextInt64();
long random = Random.Shared.NextInt64();
return new ObjectId(timestamp, random);
}
/// <summary>
/// Writes the ObjectId to the destination span (must be 12 bytes)
/// </summary>
/// <param name="destination">The destination span to write into.</param>
public void WriteTo(Span<byte> destination)
/// <summary>
/// Writes the ObjectId to the destination span (must be 12 bytes)
/// </summary>
/// <param name="destination">The destination span to write into.</param>
public void WriteTo(Span<byte> destination)
{
if (destination.Length < 12)
throw new ArgumentException("Destination must be at least 12 bytes", nameof(destination));
@@ -71,7 +70,7 @@ public readonly struct ObjectId : IEquatable<ObjectId>
}
/// <summary>
/// Converts ObjectId to byte array
/// Converts ObjectId to byte array
/// </summary>
public byte[] ToByteArray()
{
@@ -81,32 +80,47 @@ public readonly struct ObjectId : IEquatable<ObjectId>
}
/// <summary>
/// Gets timestamp portion as UTC DateTime
/// Gets timestamp portion as UTC DateTime
/// </summary>
public DateTime Timestamp => DateTimeOffset.FromUnixTimeSeconds(_timestamp).UtcDateTime;
/// <summary>
/// Determines whether this instance and another <see cref="ObjectId"/> have the same value.
/// </summary>
/// <param name="other">The object to compare with this instance.</param>
/// <returns><see langword="true"/> if the values are equal; otherwise, <see langword="false"/>.</returns>
public bool Equals(ObjectId other) =>
_timestamp == other._timestamp && _randomAndCounter == other._randomAndCounter;
/// <inheritdoc />
public override bool Equals(object? obj) => obj is ObjectId other && Equals(other);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(_timestamp, _randomAndCounter);
/// <summary>
/// Determines whether this instance and another <see cref="ObjectId" /> have the same value.
/// </summary>
/// <param name="other">The object to compare with this instance.</param>
/// <returns><see langword="true" /> if the values are equal; otherwise, <see langword="false" />.</returns>
public bool Equals(ObjectId other)
{
return _timestamp == other._timestamp && _randomAndCounter == other._randomAndCounter;
}
public static bool operator ==(ObjectId left, ObjectId right) => left.Equals(right);
public static bool operator !=(ObjectId left, ObjectId right) => !left.Equals(right);
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is ObjectId other && Equals(other);
}
/// <inheritdoc />
public override string ToString()
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(_timestamp, _randomAndCounter);
}
public static bool operator ==(ObjectId left, ObjectId right)
{
return left.Equals(right);
}
public static bool operator !=(ObjectId left, ObjectId right)
{
return !left.Equals(right);
}
/// <inheritdoc />
public override string ToString()
{
Span<byte> bytes = stackalloc byte[12];
WriteTo(bytes);
return Convert.ToHexString(bytes).ToLowerInvariant();
}
}
}

View File

@@ -1,28 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>ZB.MOM.WW.CBDD.Bson</AssemblyName>
<RootNamespace>ZB.MOM.WW.CBDD.Bson</RootNamespace>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<PackageId>ZB.MOM.WW.CBDD.Bson</PackageId>
<Version>1.3.1</Version>
<Authors>CBDD Team</Authors>
<Description>BSON Serialization Library for High-Performance Database Engine</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/EntglDb/CBDD</RepositoryUrl>
<PackageTags>database;embedded;bson;nosql;net10;zero-allocation</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>ZB.MOM.WW.CBDD.Bson</AssemblyName>
<RootNamespace>ZB.MOM.WW.CBDD.Bson</RootNamespace>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<PackageId>ZB.MOM.WW.CBDD.Bson</PackageId>
<Version>1.3.1</Version>
<Authors>CBDD Team</Authors>
<Description>BSON Serialization Library for High-Performance Database Engine</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/EntglDb/CBDD</RepositoryUrl>
<PackageTags>database;embedded;bson;nosql;net10;zero-allocation</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>