Reformat / cleanup
This commit is contained in:
@@ -1,60 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Bson;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an in-memory BSON document with lazy parsing.
|
||||
/// Uses Memory<byte> 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<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<byte>.
|
||||
/// Implemented as ref struct to ensure stack-only allocation.
|
||||
/// Zero-allocation BSON reader using ReadOnlySpan<byte>.
|
||||
/// 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..];
|
||||
}
|
||||
}
|
||||
@@ -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<byte>.
|
||||
/// Implemented as ref struct to ensure stack-only allocation.
|
||||
/// Zero-allocation BSON writer using Span<byte>.
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user