From cd3da213e1d7e2756e6f0b8cc8b317de102f80f2 Mon Sep 17 00:00:00 2001 From: BorisDog Date: Fri, 21 Nov 2025 11:38:10 -0800 Subject: [PATCH 1/2] CSHARP-5670: Implement ReadOnlyMemoryBsonReader --- src/MongoDB.Bson/IO/BsonBinaryReader.cs | 58 +- src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs | 102 +++ src/MongoDB.Bson/IO/BsonReader.cs | 17 +- src/MongoDB.Bson/IO/BsonStream.cs | 5 +- src/MongoDB.Bson/IO/BsonStreamExtensions.cs | 7 + src/MongoDB.Bson/IO/BsonTrie.cs | 21 + src/MongoDB.Bson/IO/BsonWriter.cs | 14 +- src/MongoDB.Bson/IO/INameDecoder.cs | 6 +- .../IO/ReadOnlyMemoryBsonReader.cs | 749 ++++++++++++++++++ src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs | 88 ++ .../IO/ReadOnlyMemoryReaderSettings.cs | 50 ++ src/MongoDB.Bson/IO/TrieNameDecoder.cs | 30 +- src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs | 33 + src/MongoDB.Bson/IO/Utf8Helper.cs | 28 + src/MongoDB.Bson/IO/Utf8NameDecoder.cs | 17 +- src/MongoDB.Bson/MongoDB.Bson.csproj | 1 + src/MongoDB.Bson/ObjectModel/BsonType.cs | 2 - src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs | 39 +- .../ObjectModel/LazyBsonDocument.cs | 42 +- src/MongoDB.Bson/ObjectModel/ObjectId.cs | 27 +- src/MongoDB.Bson/ObjectModel/RawBsonArray.cs | 205 +++-- .../ObjectModel/RawBsonDocument.cs | 560 ++++--------- src/MongoDB.Driver/Core/Misc/Ensure.cs | 2 +- .../Core/Operations/ChangeStreamCursor.cs | 11 +- .../CursorBatchDeserializationHelper.cs | 28 +- .../Operations/FindAndModifyOperationBase.cs | 29 +- .../CommandUsingCommandMessageWireProtocol.cs | 12 +- .../CommandUsingQueryMessageWireProtocol.cs | 1 - .../IO/ReadOnlyMemoryBufferTests.cs | 362 +++++++++ .../IO/ReadOnlyMemoryReaderTests.cs | 355 +++++++++ 30 files changed, 2209 insertions(+), 692 deletions(-) create mode 100644 src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs create mode 100644 src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs create mode 100644 src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs create mode 100644 src/MongoDB.Bson/IO/ReadOnlyMemoryReaderSettings.cs create mode 100644 src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs create mode 100644 tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryBufferTests.cs create mode 100644 tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryReaderTests.cs diff --git a/src/MongoDB.Bson/IO/BsonBinaryReader.cs b/src/MongoDB.Bson/IO/BsonBinaryReader.cs index 1cc9f8f255d..5b739c3904a 100644 --- a/src/MongoDB.Bson/IO/BsonBinaryReader.cs +++ b/src/MongoDB.Bson/IO/BsonBinaryReader.cs @@ -15,7 +15,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; namespace MongoDB.Bson.IO @@ -90,7 +89,7 @@ public BsonStream BsonStream } /// - /// Gets the settings of the writer. + /// Gets the settings of the reader. /// public new BsonBinaryReaderSettings Settings { @@ -214,7 +213,7 @@ public override BsonType ReadBsonType() { // insert the element name into the error message var periodIndex = ex.Message.IndexOf('.'); - var dottedElementName = GenerateDottedElementName(); + var dottedElementName = BsonBinaryReaderUtils.GenerateDottedElementName(_context, _contextStack.ToArray(), () => _bsonStream.ReadCString(Utf8Encodings.Lenient)); var message = ex.Message.Substring(0, periodIndex) + $" for fieldname \"{dottedElementName}\"" + ex.Message.Substring(periodIndex); throw new FormatException(message); } @@ -758,59 +757,6 @@ protected override void Dispose(bool disposing) } // private methods - private string GenerateDottedElementName() - { - string elementName; - if (_context.ContextType == ContextType.Document) - { - try - { - elementName = _bsonStream.ReadCString(Utf8Encodings.Lenient); - } - catch - { - elementName = "?"; // ignore exception - } - } - else if (_context.ContextType == ContextType.Array) - { - elementName = _context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo); - } - else - { - elementName = "?"; - } - - return GenerateDottedElementName(_contextStack.ToArray(), 0, elementName); - } - - private string GenerateDottedElementName(BsonBinaryReaderContext[] contexts, int currentContextIndex, string elementName) - { - if (currentContextIndex >= contexts.Length) - return elementName; - - var context = contexts[currentContextIndex]; - var nextIndex = currentContextIndex + 1; - - if (context.ContextType == ContextType.Document) - { - return GenerateDottedElementName(contexts, nextIndex, (context.ElementName ?? "?") + "." + elementName); - } - - if (context.ContextType == ContextType.Array) - { - var indexElementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo); - return GenerateDottedElementName(contexts, nextIndex, indexElementName + "." + elementName); - } - - if (nextIndex < contexts.Length) - { - return GenerateDottedElementName(contexts, nextIndex, "?." + elementName); - } - - return elementName; - } - private BsonReaderState GetNextState() { switch (_context.ContextType) diff --git a/src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs b/src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs new file mode 100644 index 00000000000..e3b9bf8b81a --- /dev/null +++ b/src/MongoDB.Bson/IO/BsonBinaryReaderUtils.cs @@ -0,0 +1,102 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Globalization; + +namespace MongoDB.Bson.IO; + +/// +/// Provides utility methods for working with instances in binary BSON format. +/// +public static class BsonBinaryReaderUtils +{ + /// + /// Creates an instance of for the given byte buffer and reader settings. + /// For continuous single chunk buffers an optimized implementation of is created. + /// + /// The byte buffer containing BSON data. + /// The settings to configure the BSON reader. + /// An Bson reader. + public static IBsonReader CreateBinaryReader(IByteBuffer byteBuffer, BsonBinaryReaderSettings settings) + { + if (byteBuffer is ReadOnlyMemoryBuffer readOnlyMemoryBuffer) + { + return new ReadOnlyMemoryBsonReader(readOnlyMemoryBuffer.Memory, new ReadOnlyMemoryReaderSettings(settings)); + } + + var backingBytes = byteBuffer.AccessBackingBytes(0); + if (backingBytes.Count == byteBuffer.Length) + { + return new ReadOnlyMemoryBsonReader(backingBytes, new ReadOnlyMemoryReaderSettings(settings)); + } + + var stream = new ByteBufferStream(byteBuffer, ownsBuffer: false); + return new BsonBinaryReader(stream, settings); + } + + internal static string GenerateDottedElementName(BsonBinaryReaderContext context, BsonBinaryReaderContext[] parentContexts, Func elementNameReader) + { + string elementName; + if (context.ContextType == ContextType.Document) + { + try + { + elementName = elementNameReader(); + } + catch + { + elementName = "?"; // ignore exception + } + } + else if (context.ContextType == ContextType.Array) + { + elementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo); + } + else + { + elementName = "?"; + } + + return GenerateDottedElementName(parentContexts, 0, elementName); + } + + private static string GenerateDottedElementName(BsonBinaryReaderContext[] contexts, int currentContextIndex, string elementName) + { + if (currentContextIndex >= contexts.Length) + return elementName; + + var context = contexts[currentContextIndex]; + var nextIndex = currentContextIndex + 1; + + if (context.ContextType == ContextType.Document) + { + return GenerateDottedElementName(contexts, nextIndex, (context.ElementName ?? "?") + "." + elementName); + } + + if (context.ContextType == ContextType.Array) + { + var indexElementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo); + return GenerateDottedElementName(contexts, nextIndex, indexElementName + "." + elementName); + } + + if (nextIndex < contexts.Length) + { + return GenerateDottedElementName(contexts, nextIndex, "?." + elementName); + } + + return elementName; + } +} \ No newline at end of file diff --git a/src/MongoDB.Bson/IO/BsonReader.cs b/src/MongoDB.Bson/IO/BsonReader.cs index 25724b855e7..928f0423560 100644 --- a/src/MongoDB.Bson/IO/BsonReader.cs +++ b/src/MongoDB.Bson/IO/BsonReader.cs @@ -466,9 +466,17 @@ protected void ThrowObjectDisposedException() /// /// The name of the method calling this one. /// The required BSON type. - protected void VerifyBsonType(string methodName, BsonType requiredBsonType) + protected void VerifyBsonType(string methodName, BsonType requiredBsonType) => + VerifyBsonType(requiredBsonType, methodName); + + /// + /// Verifies the current state and BsonType of the reader. + /// + /// /// The required BSON type. + /// The name of the method calling this one. + protected void VerifyBsonType(BsonType requiredBsonType, [System.Runtime.CompilerServices.CallerMemberName]string methodName = null) { - if (_state == BsonReaderState.Initial || _state == BsonReaderState.ScopeDocument || _state == BsonReaderState.Type) + if (_state is BsonReaderState.Initial or BsonReaderState.ScopeDocument or BsonReaderState.Type) { ReadBsonType(); } @@ -483,10 +491,7 @@ protected void VerifyBsonType(string methodName, BsonType requiredBsonType) } if (_currentBsonType != requiredBsonType) { - var message = string.Format( - "{0} can only be called when CurrentBsonType is {1}, not when CurrentBsonType is {2}.", - methodName, requiredBsonType, _currentBsonType); - throw new InvalidOperationException(message); + throw new InvalidOperationException($"{methodName} can only be called when CurrentBsonType is {requiredBsonType}, not when CurrentBsonType is {_currentBsonType}."); } } } diff --git a/src/MongoDB.Bson/IO/BsonStream.cs b/src/MongoDB.Bson/IO/BsonStream.cs index 20268155c38..12dc985d3f3 100644 --- a/src/MongoDB.Bson/IO/BsonStream.cs +++ b/src/MongoDB.Bson/IO/BsonStream.cs @@ -14,16 +14,13 @@ */ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace MongoDB.Bson.IO { /// - /// Represents a Stream has additional methods to suport reading and writing BSON values. + /// Represents a Stream has additional methods to support reading and writing BSON values. /// public abstract class BsonStream : Stream { diff --git a/src/MongoDB.Bson/IO/BsonStreamExtensions.cs b/src/MongoDB.Bson/IO/BsonStreamExtensions.cs index 2f3fcc1a557..dbd870fc06d 100644 --- a/src/MongoDB.Bson/IO/BsonStreamExtensions.cs +++ b/src/MongoDB.Bson/IO/BsonStreamExtensions.cs @@ -65,6 +65,13 @@ public static void BackpatchSize(this BsonStream stream, long startPosition) stream.Position = endPosition; } + /// + /// Determines whether the specified BSON type is valid. + /// + /// The BSON type to validate. + /// True if the BSON type is valid; otherwise, false. + public static bool IsValidBsonType(BsonType bsonType) => __validBsonTypes[(byte)bsonType]; + /// /// Reads the binary sub type. /// diff --git a/src/MongoDB.Bson/IO/BsonTrie.cs b/src/MongoDB.Bson/IO/BsonTrie.cs index 6c276113b44..ecaad36a0ff 100644 --- a/src/MongoDB.Bson/IO/BsonTrie.cs +++ b/src/MongoDB.Bson/IO/BsonTrie.cs @@ -93,6 +93,27 @@ public bool TryGetNode(ArraySegment utf8, out BsonTrieNode node) return node != null; } + /// + /// Gets the node associated with the specified element name. + /// + /// The element name. + /// + /// When this method returns, contains the node associated with the specified element name, if the key is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// True if the node was found; otherwise, false. + public bool TryGetNode(ReadOnlySpan utf8, out BsonTrieNode node) + { + node = _root; + for (var i = 0; node != null && i < utf8.Length; i++) + { + var keyByte = utf8[i]; + node = node.GetChild(keyByte); + } + + return node != null; + } + /// /// Tries to get the node associated with a name read from a stream. /// diff --git a/src/MongoDB.Bson/IO/BsonWriter.cs b/src/MongoDB.Bson/IO/BsonWriter.cs index 3fd705fa31b..a264ff40d9d 100644 --- a/src/MongoDB.Bson/IO/BsonWriter.cs +++ b/src/MongoDB.Bson/IO/BsonWriter.cs @@ -347,15 +347,13 @@ public virtual void WriteRawBsonDocument(IByteBuffer slice) // overridden in BsonBinaryWriter to write the raw bytes to the stream // for all other streams, deserialize the raw bytes and serialize the resulting document instead - using (var stream = new ByteBufferStream(slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, BsonBinaryReaderSettings.Defaults)) - { - var deserializationContext = BsonDeserializationContext.CreateRoot(bsonReader); - var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(slice, BsonBinaryReaderSettings.Defaults); - var serializationContext = BsonSerializationContext.CreateRoot(this); - BsonDocumentSerializer.Instance.Serialize(serializationContext, document); - } + var deserializationContext = BsonDeserializationContext.CreateRoot(bsonReader); + var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext); + + var serializationContext = BsonSerializationContext.CreateRoot(this); + BsonDocumentSerializer.Instance.Serialize(serializationContext, document); } /// diff --git a/src/MongoDB.Bson/IO/INameDecoder.cs b/src/MongoDB.Bson/IO/INameDecoder.cs index 06cd0cf11d0..e11cb168f7f 100644 --- a/src/MongoDB.Bson/IO/INameDecoder.cs +++ b/src/MongoDB.Bson/IO/INameDecoder.cs @@ -14,7 +14,6 @@ */ using System; -using System.IO; using System.Text; namespace MongoDB.Bson.IO @@ -40,4 +39,9 @@ public interface INameDecoder /// The name. void Inform(string name); } + + internal interface INameDecoderInternal + { + string Decode(ReadOnlySpan span, UTF8Encoding encoding); + } } diff --git a/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs b/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs new file mode 100644 index 00000000000..0f53ff16cbd --- /dev/null +++ b/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs @@ -0,0 +1,749 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MongoDB.Bson.IO; + +/// +/// Represents a BSON reader for a ReadOnlyMemory containing a binary BSON byte array. +/// +public sealed class ReadOnlyMemoryBsonReader : BsonReader +{ + private static readonly BsonReaderState[] __stateMap; + + private readonly ReadOnlyMemory _memory; + private int _position; + + private BsonBinaryReaderContext _context; + private readonly Stack _contextStack = new(4); + + static ReadOnlyMemoryBsonReader() + { + var count = Enum.GetValues(typeof(ContextType)).Length; + __stateMap = Enumerable.Repeat(BsonReaderState.Closed, count).ToArray(); + __stateMap[(int)ContextType.Array] = BsonReaderState.Type; + __stateMap[(int)ContextType.Document] = BsonReaderState.Type; + __stateMap[(int)ContextType.ScopeDocument] = BsonReaderState.Type; + __stateMap[(int)ContextType.TopLevel] = BsonReaderState.Initial; + } + + /// + /// Initializes a new instance of the ReadOnlyMemoryBsonReader class. + /// + /// Memory containing binary BSON. + public ReadOnlyMemoryBsonReader(ReadOnlyMemory memory) + : this(memory, ReadOnlyMemoryReaderSettings.Defaults) + { + } + + /// + /// Initializes a new instance of the ReadOnlyMemoryBsonReader class. + /// + /// Memory containing binary BSON. + /// A ReadOnlyMemoryReaderSettings. + public ReadOnlyMemoryBsonReader(ReadOnlyMemory memory, ReadOnlyMemoryReaderSettings settings) + : base(settings) + { + _memory = memory; + _position = 0; + + _context = new BsonBinaryReaderContext(ContextType.TopLevel, 0, 0); + } + + /// + /// Gets or sets the current position within the BSON data. + /// + /// + /// Thrown when the assigned value is less than 0 or greater than the length of the BSON data. + /// + public int Position + { + get => _position; + set + { + if (value < 0 || value > _memory.Length) + { + throw new ArgumentOutOfRangeException(nameof(value), $"Valid range is [{0}..{_memory.Length}]"); + } + + _position = value; + } + } + + /// + /// Gets the settings of the reader. + /// + public new ReadOnlyMemoryReaderSettings Settings => (ReadOnlyMemoryReaderSettings)base.Settings; + + /// + public override void Close() + { + // Close can be called on Disposed objects + State = BsonReaderState.Closed; + } + + /// + public override BsonReaderBookmark GetBookmark() => + new BsonBinaryReaderBookmark(State, CurrentBsonType, CurrentName, _context, _contextStack, _position); + + /// + public override bool IsAtEndOfFile() => _position >= _memory.Length; + + /// +#pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary + public override BsonBinaryData ReadBinaryData() + { + VerifyBsonTypeAndSetNextState(BsonType.Binary); + + var dataSize = ReadSize(); + var totalSize = dataSize + 1; // data + subtype + var span = _memory.Span.Slice(_position, totalSize); + _position += totalSize; + + var subType = (BsonBinarySubType)span[0]; + if (subType == BsonBinarySubType.OldBinary) + { + // sub type OldBinary has two sizes (for historical reasons) + int dataSize2 = ReadSize(); + if (dataSize2 != dataSize - 4) + { + throw new FormatException("Binary sub type OldBinary has inconsistent sizes"); + } + dataSize = dataSize2; + + if (Settings.FixOldBinarySubTypeOnInput) + { + subType = BsonBinarySubType.Binary; // replace obsolete OldBinary with new Binary sub type + } + } + + var bytes = span.Slice(1, dataSize).ToArray(); + + if ((subType == BsonBinarySubType.UuidStandard || subType == BsonBinarySubType.UuidLegacy) && + bytes.Length != 16) + { + throw new FormatException($"Length must be 16, not {bytes.Length}, when subType is {subType}."); + } + + return new BsonBinaryData(bytes, subType); + } +#pragma warning restore 618 + + /// + public override bool ReadBoolean() + { + VerifyBsonTypeAndSetNextState(BsonType.Boolean); + + var b = _memory.Span[_position++]; + + return b switch + { + 0 => false, + 1 => true, + _ => throw new FormatException($"Invalid BsonBoolean value: {b}.") + }; + } + + /// + public override BsonType ReadBsonType() + { + var state = State; + + if (state is BsonReaderState.Initial or BsonReaderState.ScopeDocument) + { + // there is an implied type of Document for the top level and for scope documents + CurrentBsonType = BsonType.Document; + State = BsonReaderState.Value; + return CurrentBsonType; + } + if (state != BsonReaderState.Type) + { + ThrowInvalidState(nameof(ReadBsonType), BsonReaderState.Type); + } + + if (_context.ContextType == ContextType.Array) + { + _context.ArrayIndex++; + } + + CurrentBsonType = (BsonType)_memory.Span[_position++]; + + if (!BsonStreamExtensions.IsValidBsonType(CurrentBsonType)) + { + var dottedElementName = BsonBinaryReaderUtils.GenerateDottedElementName(_context, _contextStack.ToArray(), () => ReadCStringFromMemory(Utf8Encodings.Lenient)); + throw new FormatException($"Detected unknown BSON type \"\\x{(int)CurrentBsonType:x2}\" for fieldname \"{dottedElementName}\". Are you using the latest driver version?"); + } + + if (CurrentBsonType == BsonType.EndOfDocument) + { + switch (_context.ContextType) + { + case ContextType.Array: + State = BsonReaderState.EndOfArray; + return BsonType.EndOfDocument; + case ContextType.Document: + case ContextType.ScopeDocument: + State = BsonReaderState.EndOfDocument; + return BsonType.EndOfDocument; + default: + throw new FormatException($"BsonType EndOfDocument is not valid when ContextType is {_context.ContextType}."); + } + } + + switch (_context.ContextType) + { + case ContextType.Array: + SkipCString(); + State = BsonReaderState.Value; + break; + case ContextType.Document: + case ContextType.ScopeDocument: + State = BsonReaderState.Name; + break; + default: + throw new BsonInternalException("Unexpected ContextType."); + } + + return CurrentBsonType; + } + + private void SkipCString() + { + var offset = _memory.Span.Slice(_position).IndexOf((byte)0); + _position += offset + 1; + } + + /// + public override byte[] ReadBytes() + { + VerifyBsonTypeAndSetNextState(BsonType.Binary); + + var size = ReadSize(); + var subType = (BsonBinarySubType)_memory.Span[_position++]; + +#pragma warning disable 618 + if (subType != BsonBinarySubType.Binary && subType != BsonBinarySubType.OldBinary) + { + throw new FormatException($"{nameof(ReadBytes)} requires the binary sub type to be Binary, not {subType}."); + } +#pragma warning restore 618 + + var result = _memory.Span.Slice(_position, size).ToArray(); + _position += size; + + return result; + } + + /// + public override long ReadDateTime() + { + VerifyBsonTypeAndSetNextState(BsonType.DateTime); + + var value = ReadInt64FromMemory(); + + if (value == BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch + 1) + { + if (Settings.FixOldDateTimeMaxValueOnInput) + { + value = BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch; + } + } + return value; + } + + /// + public override Decimal128 ReadDecimal128() + { + VerifyBsonTypeAndSetNextState(BsonType.Decimal128); + + var lowBits = (ulong)ReadInt64FromMemory(); + var highBits = (ulong)ReadInt64FromMemory(); + return Decimal128.FromIEEEBits(highBits, lowBits); + } + + /// + public override double ReadDouble() + { + VerifyBsonTypeAndSetNextState(BsonType.Double); + + var result = BinaryPrimitivesCompat.ReadDoubleLittleEndian(_memory.Span.Slice(_position, 8)); + _position += 8; + + return result; + } + + /// + public override void ReadEndArray() + { + if (_context.ContextType != ContextType.Array) + { + ThrowInvalidContextType(nameof(ReadEndArray), _context.ContextType, ContextType.Array); + } + if (State == BsonReaderState.Type) + { + ReadBsonType(); // will set state to EndOfArray if at end of array + } + if (State != BsonReaderState.EndOfArray) + { + ThrowInvalidState(nameof(ReadEndArray), BsonReaderState.EndOfArray); + } + + PopContext(); + + switch (_context.ContextType) + { + case ContextType.Array: State = BsonReaderState.Type; break; + case ContextType.Document: State = BsonReaderState.Type; break; + case ContextType.TopLevel: State = BsonReaderState.Initial; break; + default: throw new BsonInternalException("Unexpected ContextType."); + } + } + + /// + public override void ReadEndDocument() + { + if (_context.ContextType != ContextType.Document && _context.ContextType != ContextType.ScopeDocument) + { + ThrowInvalidContextType(nameof(ReadEndDocument), _context.ContextType, ContextType.Document, ContextType.ScopeDocument); + } + if (State == BsonReaderState.Type) + { + ReadBsonType(); // will set state to EndOfDocument if at end of document + } + if (State != BsonReaderState.EndOfDocument) + { + ThrowInvalidState(nameof(ReadEndDocument), BsonReaderState.EndOfDocument); + } + + PopContext(); + if (_context.ContextType == ContextType.JavaScriptWithScope) + { + PopContext(); // JavaScriptWithScope + } + switch (_context.ContextType) + { + case ContextType.Array: State = BsonReaderState.Type; break; + case ContextType.Document: State = BsonReaderState.Type; break; + case ContextType.TopLevel: State = BsonReaderState.Initial; break; + default: throw new BsonInternalException("Unexpected ContextType."); + } + } + + /// + public override int ReadInt32() + { + VerifyBsonTypeAndSetNextState(BsonType.Int32); + + return ReadInt32FromMemory(); + } + + /// + public override long ReadInt64() + { + VerifyBsonTypeAndSetNextState(BsonType.Int64); + + return ReadInt64FromMemory(); + } + + /// + public override string ReadJavaScript() + { + VerifyBsonTypeAndSetNextState(BsonType.JavaScript); + + return ReadStringFromMemory(); + } + + /// + public override string ReadJavaScriptWithScope() + { + VerifyBsonType(BsonType.JavaScriptWithScope); + + var startPosition = _position; + var size = ReadSize(); + + PushContext(new(ContextType.JavaScriptWithScope, startPosition, size)); + + var code = ReadStringFromMemory(); + + State = BsonReaderState.ScopeDocument; + return code; + } + + /// + public override void ReadMaxKey() => + VerifyBsonTypeAndSetNextState(BsonType.MaxKey); + + /// + public override void ReadMinKey() => + VerifyBsonTypeAndSetNextState(BsonType.MinKey); + + /// + public override string ReadName(INameDecoder nameDecoder) + { + if (State == BsonReaderState.Type) + { + ReadBsonType(); + } + if (State != BsonReaderState.Name) + { + ThrowInvalidState(nameof(ReadName), BsonReaderState.Name); + } + + var span = _memory.Span.Slice(_position); + var nameEndIndex = span.IndexOf((byte)0); + var nameSpan = span.Slice(0, nameEndIndex); + + if (nameDecoder is INameDecoderInternal nameDecoderInternal) + { + CurrentName = nameDecoderInternal.Decode(nameSpan, Settings.Encoding); + } + else + { + throw new Exception($"Name decoder {nameDecoder.GetType()} is not supported"); + } + + _position += nameEndIndex + 1; + + State = BsonReaderState.Value; + + if (_context.ContextType == ContextType.Document) + { + _context.ElementName = CurrentName; + } + + return CurrentName; + } + + /// + public override void ReadNull() => + VerifyBsonTypeAndSetNextState(BsonType.Null); + + /// + public override ObjectId ReadObjectId() + { + VerifyBsonTypeAndSetNextState(BsonType.ObjectId); + + var result = new ObjectId(_memory.Span.Slice(_position, 12)); + _position += 12; + + return result; + } + + /// + /// Reads a raw BSON array. WARNING TODO + /// + /// + /// The raw BSON array. + /// + public override IByteBuffer ReadRawBsonArray() + { + VerifyBsonType(BsonType.Array); + + var slice = ReadSliceFromMemory(); + + switch (_context.ContextType) + { + case ContextType.Array: State = BsonReaderState.Type; break; + case ContextType.Document: State = BsonReaderState.Type; break; + case ContextType.TopLevel: State = BsonReaderState.Initial; break; + default: throw new BsonInternalException("Unexpected ContextType."); + } + + return slice; + } + + /// + /// Reads a raw BSON document. + /// + /// + /// The raw BSON document. + /// + public override IByteBuffer ReadRawBsonDocument() + { + VerifyBsonType(BsonType.Document); + + var slice = ReadSliceFromMemory(); + + if (_context.ContextType == ContextType.JavaScriptWithScope) + { + PopContext(); // JavaScriptWithScope + } + switch (_context.ContextType) + { + case ContextType.Array: State = BsonReaderState.Type; break; + case ContextType.Document: State = BsonReaderState.Type; break; + case ContextType.TopLevel: State = BsonReaderState.Initial; break; + default: throw new BsonInternalException("Unexpected ContextType."); + } + + return slice; + } + + /// + public override BsonRegularExpression ReadRegularExpression() + { + VerifyBsonTypeAndSetNextState(BsonType.RegularExpression); + + var pattern = ReadCStringFromMemory(); + var options = ReadCStringFromMemory(); + return new(pattern, options); + } + + /// + public override void ReadStartArray() + { + VerifyBsonType(BsonType.Array); + + var startPosition = _position; + var size = ReadSize(); + + PushContext(new(ContextType.Array, startPosition, size)); + + State = BsonReaderState.Type; + } + + /// + public override void ReadStartDocument() + { + VerifyBsonType(BsonType.Document); + + var contextType = (State == BsonReaderState.ScopeDocument) ? ContextType.ScopeDocument : ContextType.Document; + var startPosition = _position; + var size = ReadSize(); + + PushContext(new(contextType, startPosition, size)); + + State = BsonReaderState.Type; + } + + /// + public override string ReadString() + { + VerifyBsonTypeAndSetNextState(BsonType.String); + + return ReadStringFromMemory(); + } + + /// + public override string ReadSymbol() + { + VerifyBsonTypeAndSetNextState(BsonType.Symbol); + + return ReadStringFromMemory(); + } + + /// + public override long ReadTimestamp() + { + VerifyBsonTypeAndSetNextState(BsonType.Timestamp); + + return ReadInt64FromMemory(); + } + + /// + public override void ReadUndefined() + { + VerifyBsonTypeAndSetNextState(BsonType.Undefined); + } + + /// + public override void ReturnToBookmark(BsonReaderBookmark bookmark) + { + var binaryReaderBookmark = (BsonBinaryReaderBookmark)bookmark; + State = binaryReaderBookmark.State; + CurrentBsonType = binaryReaderBookmark.CurrentBsonType; + CurrentName = binaryReaderBookmark.CurrentName; + _context = binaryReaderBookmark.RestoreContext(_contextStack); + _position = checked((int)binaryReaderBookmark.Position); + } + + /// + public override void SkipName() + { + if (State != BsonReaderState.Name) + { + ThrowInvalidState(nameof(SkipName), BsonReaderState.Name); + } + + SkipCString(); + + CurrentName = null; + State = BsonReaderState.Value; + + if (_context.ContextType == ContextType.Document) + { + _context.ElementName = CurrentName; + } + } + + /// + public override void SkipValue() + { + if (State != BsonReaderState.Value) + { + ThrowInvalidState(nameof(SkipValue), BsonReaderState.Value); + } + + int skip; + switch (CurrentBsonType) + { + case BsonType.Array: skip = ReadSize() - 4; break; + case BsonType.Binary: skip = ReadSize() + 1; break; + case BsonType.Boolean: skip = 1; break; + case BsonType.DateTime: skip = 8; break; + case BsonType.Document: skip = ReadSize() - 4; break; + case BsonType.Decimal128: skip = 16; break; + case BsonType.Double: skip = 8; break; + case BsonType.Int32: skip = 4; break; + case BsonType.Int64: skip = 8; break; + case BsonType.JavaScript: skip = ReadSize(); break; + case BsonType.JavaScriptWithScope: skip = ReadSize() - 4; break; + case BsonType.MaxKey: skip = 0; break; + case BsonType.MinKey: skip = 0; break; + case BsonType.Null: skip = 0; break; + case BsonType.ObjectId: skip = 12; break; + case BsonType.RegularExpression: SkipCString(); SkipCString(); skip = 0; break; + case BsonType.String: skip = ReadSize(); break; + case BsonType.Symbol: skip = ReadSize(); break; + case BsonType.Timestamp: skip = 8; break; + case BsonType.Undefined: skip = 0; break; + default: throw new BsonInternalException("Unexpected BsonType."); + } + + _position += skip; + State = BsonReaderState.Type; + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + try + { + Close(); + } + catch + { + // ignore exceptions + } + } + base.Dispose(disposing); + } + + private void PopContext() + { + var actualSize = _position - _context.StartPosition; + if (actualSize != _context.Size) + { + throw new FormatException($"Expected size to be {_context.Size}, not {actualSize}."); + } + + _context =_contextStack.Pop(); + } + + private void PushContext(BsonBinaryReaderContext newContext) + { + _contextStack.Push(_context); + _context = newContext; + } + + private int ReadSize() + { + var size = ReadInt32FromMemory(); + + if (size < 0) + { + throw new FormatException($"Size {size} is not valid because it is negative."); + } + if (size > Settings.MaxDocumentSize) + { + throw new FormatException($"Size {size} is not valid because it is larger than MaxDocumentSize {Settings.MaxDocumentSize}."); + } + + return size; + } + + private int ReadInt32FromMemory() + { + var result = BinaryPrimitives.ReadInt32LittleEndian(_memory.Span.Slice(_position)); + _position += 4; + return result; + } + + private long ReadInt64FromMemory() + { + var result = BinaryPrimitives.ReadInt64LittleEndian(_memory.Span.Slice(_position)); + _position += 8; + return result; + } + + private IByteBuffer ReadSliceFromMemory() + { + var memoryAtPosition = _memory.Slice(_position); + var length = BinaryPrimitives.ReadInt32LittleEndian(memoryAtPosition.Span); + + var result = new ReadOnlyMemoryBuffer(memoryAtPosition.Slice(0, length)); + + _position += length; + + return result; + } + + private string ReadStringFromMemory() + { + var span = _memory.Span.Slice(_position); + var length = BinaryPrimitives.ReadInt32LittleEndian(span); + + if (span[4 + length - 1] != 0) + { + throw new FormatException("String is missing terminating null byte."); + } + + var result = Utf8Helper.DecodeUtf8String(span.Slice(4, length - 1), Settings.Encoding); + _position += length + 4; + + return result; + } + + private string ReadCStringFromMemory(UTF8Encoding encoding = null) + { + var span = _memory.Span.Slice(_position); + var index = span.IndexOf((byte)0); + + var result = Utf8Helper.DecodeUtf8String(span.Slice(0, index), encoding ?? Settings.Encoding); + _position += index + 1; + + return result; + } + + private void VerifyBsonTypeAndSetNextState(BsonType requiredBsonType, [System.Runtime.CompilerServices.CallerMemberName]string methodName = null) + { + VerifyBsonType(requiredBsonType, methodName); + var nextState = __stateMap[(int)_context.ContextType]; + + if (nextState == BsonReaderState.Closed) + { + throw new BsonInternalException($"Unexpected ContextType {_context.ContextType}."); + } + + State = nextState; + } +} diff --git a/src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs b/src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs new file mode 100644 index 00000000000..cc95e1caf11 --- /dev/null +++ b/src/MongoDB.Bson/IO/ReadOnlyMemoryBuffer.cs @@ -0,0 +1,88 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; + +namespace MongoDB.Bson.IO; + +internal sealed class ReadOnlyMemoryBuffer : IByteBuffer +{ + private readonly ReadOnlyMemory _memory; + + public ReadOnlyMemoryBuffer(ReadOnlyMemory memory) + { + _memory = memory; + } + + /// + public int Capacity => _memory.Length; + + /// + public bool IsReadOnly => true; + + /// + public int Length + { + get => _memory.Length; + set => ThrowNotWritableException(); + } + + public ReadOnlyMemory Memory => _memory; + + /// + public ArraySegment AccessBackingBytes(int position) => + new ArraySegment(_memory.Slice(position).ToArray()); + + /// + public void Clear(int position, int count) => + ThrowNotWritableException(); + + /// + public void Dispose() + { + } + + /// + public void EnsureCapacity(int minimumCapacity) => + ThrowNotWritableException(); + + /// + public byte GetByte(int position) => + _memory.Span[position]; + + /// + public void GetBytes(int position, byte[] destination, int offset, int count) => + _memory.Span.Slice(position, count).CopyTo(new Span(destination, offset, count)); + + /// + public IByteBuffer GetSlice(int position, int length) => + new ReadOnlyMemoryBuffer(_memory.Slice(position, length)); + + /// + public void MakeReadOnly() + { + } + + /// + public void SetByte(int position, byte value) => + ThrowNotWritableException(); + + /// + public void SetBytes(int position, byte[] source, int offset, int count) => + ThrowNotWritableException(); + + private static void ThrowNotWritableException() => + throw new InvalidOperationException($"{nameof(ReadOnlyMemoryBuffer)} is not writable."); +} diff --git a/src/MongoDB.Bson/IO/ReadOnlyMemoryReaderSettings.cs b/src/MongoDB.Bson/IO/ReadOnlyMemoryReaderSettings.cs new file mode 100644 index 00000000000..48030f74bb7 --- /dev/null +++ b/src/MongoDB.Bson/IO/ReadOnlyMemoryReaderSettings.cs @@ -0,0 +1,50 @@ +namespace MongoDB.Bson.IO; + +/// +/// Represents settings for a . +/// +public sealed class ReadOnlyMemoryReaderSettings : BsonBinaryReaderSettings +{ + // constructors + /// + /// Initializes a new instance of the ReadOnlyMemoryReaderSettings class. + /// + public ReadOnlyMemoryReaderSettings() + { + } + + internal ReadOnlyMemoryReaderSettings(BsonBinaryReaderSettings readerSettings) + { + Encoding = readerSettings.Encoding; + FixOldBinarySubTypeOnInput = readerSettings.FixOldBinarySubTypeOnInput; + FixOldDateTimeMaxValueOnInput = readerSettings.FixOldDateTimeMaxValueOnInput; + MaxDocumentSize = readerSettings.MaxDocumentSize; + } + + // public static properties + /// + /// Gets the default settings for a + /// + public static new ReadOnlyMemoryReaderSettings Defaults { get; } = new(); + + // public methods + /// + /// Creates a clone of the settings. + /// + /// A clone of the settings. + public new ReadOnlyMemoryReaderSettings Clone() => (ReadOnlyMemoryReaderSettings)CloneImplementation(); + + // protected methods + /// + /// Creates a clone of the settings. + /// + /// A clone of the settings. + protected override BsonReaderSettings CloneImplementation() => + new ReadOnlyMemoryReaderSettings + { + Encoding = Encoding, + FixOldBinarySubTypeOnInput = FixOldBinarySubTypeOnInput, + FixOldDateTimeMaxValueOnInput = FixOldDateTimeMaxValueOnInput, + MaxDocumentSize = MaxDocumentSize + }; +} diff --git a/src/MongoDB.Bson/IO/TrieNameDecoder.cs b/src/MongoDB.Bson/IO/TrieNameDecoder.cs index 88b93701ef6..ab5f0fbd060 100644 --- a/src/MongoDB.Bson/IO/TrieNameDecoder.cs +++ b/src/MongoDB.Bson/IO/TrieNameDecoder.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System; using System.Text; namespace MongoDB.Bson.IO @@ -21,7 +22,7 @@ namespace MongoDB.Bson.IO /// Represents a Trie-based name decoder that also provides a value. /// /// The type of the value. - public class TrieNameDecoder : INameDecoder + public class TrieNameDecoder : INameDecoder, INameDecoderInternal { // private fields private bool _found; @@ -92,6 +93,33 @@ public string Decode(BsonStream stream, UTF8Encoding encoding) return stream.ReadCString(encoding); } + /// + /// Reads the name. + /// + /// The span. + /// The encoding. + /// + /// The name. + /// + public string Decode(ReadOnlySpan span, UTF8Encoding encoding) + { + BsonTrieNode node; + if (_trie.TryGetNode(span, out node)) + { + if (node.HasValue) + { + _found = true; + _value = node.Value; + return node.ElementName; + } + } + + _found = false; + _value = default; + + return encoding.GetString(span); + } + /// /// Informs the decoder of an already decoded name (so the decoder can change state if necessary). /// diff --git a/src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs b/src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs new file mode 100644 index 00000000000..f89cf474ccc --- /dev/null +++ b/src/MongoDB.Bson/IO/Utf8EncodingExtensions.cs @@ -0,0 +1,33 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Text; + +namespace MongoDB.Bson.IO +{ + internal static class Utf8EncodingExtensions + { +#if NET472 + public static unsafe string GetString(this UTF8Encoding encoding, ReadOnlySpan span) + { + fixed (byte* ptr = span) + { + return encoding.GetString(ptr, span.Length); + } + } +#endif + } +} diff --git a/src/MongoDB.Bson/IO/Utf8Helper.cs b/src/MongoDB.Bson/IO/Utf8Helper.cs index e165f470806..d531b458542 100644 --- a/src/MongoDB.Bson/IO/Utf8Helper.cs +++ b/src/MongoDB.Bson/IO/Utf8Helper.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System; using System.Text; namespace MongoDB.Bson.IO @@ -63,5 +64,32 @@ public static string DecodeUtf8String(byte[] bytes, int index, int count, UTF8En return encoding.GetString(bytes, index, count); } + + /// + /// Decodes a UTF8 string. + /// + /// The span. + /// The encoding. + /// The decoded string. + public static string DecodeUtf8String(ReadOnlySpan span, UTF8Encoding encoding) + { + switch (span.Length) + { + // special case empty strings + case 0: + return ""; + + // special case single character strings + case 1: + var byte1 = (int)span[0]; + if (byte1 < __asciiStringTable.Length) + { + return __asciiStringTable[byte1]; + } + break; + } + + return encoding.GetString(span); + } } } diff --git a/src/MongoDB.Bson/IO/Utf8NameDecoder.cs b/src/MongoDB.Bson/IO/Utf8NameDecoder.cs index e6204a4ac66..3e1596e9b6e 100644 --- a/src/MongoDB.Bson/IO/Utf8NameDecoder.cs +++ b/src/MongoDB.Bson/IO/Utf8NameDecoder.cs @@ -14,17 +14,17 @@ */ using System; -using System.IO; using System.Text; + namespace MongoDB.Bson.IO { /// /// Represents a UTF8 name decoder. /// - public class Utf8NameDecoder : INameDecoder + public class Utf8NameDecoder : INameDecoder, INameDecoderInternal { // private static fields - private static readonly Utf8NameDecoder __instance = new Utf8NameDecoder(); + private static readonly Utf8NameDecoder __instance = new(); // public static properties /// @@ -53,6 +53,17 @@ public string Decode(BsonStream stream, UTF8Encoding encoding) return Utf8Helper.DecodeUtf8String(utf8.Array, utf8.Offset, utf8.Count, encoding); } + /// + /// Decodes the name. + /// + /// The span. + /// The encoding. + /// + /// The name. + /// + public string Decode(ReadOnlySpan span, UTF8Encoding encoding) => + Utf8Helper.DecodeUtf8String(span, encoding); + /// /// Informs the decoder of an already decoded name (so the decoder can change state if necessary). /// diff --git a/src/MongoDB.Bson/MongoDB.Bson.csproj b/src/MongoDB.Bson/MongoDB.Bson.csproj index 71ffb458fa9..780170537fc 100644 --- a/src/MongoDB.Bson/MongoDB.Bson.csproj +++ b/src/MongoDB.Bson/MongoDB.Bson.csproj @@ -2,6 +2,7 @@ ..\..\MongoDBLegacy.ruleset + true diff --git a/src/MongoDB.Bson/ObjectModel/BsonType.cs b/src/MongoDB.Bson/ObjectModel/BsonType.cs index ff3ed6d7402..db9cf899fd0 100644 --- a/src/MongoDB.Bson/ObjectModel/BsonType.cs +++ b/src/MongoDB.Bson/ObjectModel/BsonType.cs @@ -13,8 +13,6 @@ * limitations under the License. */ -using System; - namespace MongoDB.Bson { /// diff --git a/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs b/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs index f9232489f4f..4b066dc9aaa 100644 --- a/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs +++ b/src/MongoDB.Bson/ObjectModel/LazyBsonArray.cs @@ -32,8 +32,8 @@ public class LazyBsonArray : MaterializedOnDemandBsonArray { // private fields private IByteBuffer _slice; - private List _disposableItems = new List(); - private BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults; + private List _disposableItems = new(); + private readonly BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults; // constructors /// @@ -150,7 +150,7 @@ private IByteBuffer CloneSlice() return _slice.GetSlice(0, _slice.Length); } - private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader) + private LazyBsonArray DeserializeLazyBsonArray(IBsonReader bsonReader) { var slice = bsonReader.ReadRawBsonArray(); var nestedArray = new LazyBsonArray(slice); @@ -158,7 +158,7 @@ private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader) return nestedArray; } - private LazyBsonDocument DeserializeLazyBsonDocument(BsonBinaryReader bsonReader) + private LazyBsonDocument DeserializeLazyBsonDocument(IBsonReader bsonReader) { var slice = bsonReader.ReadRawBsonDocument(); var nestedDocument = new LazyBsonDocument(slice); @@ -170,27 +170,24 @@ private IEnumerable MaterializeThisLevel() { var values = new List(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - BsonType bsonType; - while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument) + bsonReader.ReadStartDocument(); + BsonType bsonType; + while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + BsonValue value; + switch (bsonType) { - bsonReader.SkipName(); - BsonValue value; - switch (bsonType) - { - case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break; - case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break; - default: value = BsonValueSerializer.Instance.Deserialize(context); break; - } - values.Add(value); + case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break; + case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break; + default: value = BsonValueSerializer.Instance.Deserialize(context); break; } - bsonReader.ReadEndDocument(); + values.Add(value); } + bsonReader.ReadEndDocument(); return values; } diff --git a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs index a95e766a3b8..977ad883bb8 100644 --- a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs @@ -14,10 +14,7 @@ */ using System; -using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Linq; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; @@ -33,8 +30,8 @@ public class LazyBsonDocument : MaterializedOnDemandBsonDocument { // private fields private IByteBuffer _slice; - private List _disposableItems = new List(); - private BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults; + private List _disposableItems = new(); + private readonly BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults; // constructors /// @@ -161,7 +158,7 @@ private IByteBuffer CloneSlice() return _slice.GetSlice(0, _slice.Length); } - private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader) + private LazyBsonArray DeserializeLazyBsonArray(IBsonReader bsonReader) { var slice = bsonReader.ReadRawBsonArray(); var nestedArray = new LazyBsonArray(slice); @@ -169,7 +166,7 @@ private LazyBsonArray DeserializeLazyBsonArray(BsonBinaryReader bsonReader) return nestedArray; } - private LazyBsonDocument DeserializeLazyBsonDocument(BsonBinaryReader bsonReader) + private LazyBsonDocument DeserializeLazyBsonDocument(IBsonReader bsonReader) { var slice = bsonReader.ReadRawBsonDocument(); var nestedDocument = new LazyBsonDocument(slice); @@ -181,27 +178,24 @@ private IEnumerable MaterializeThisLevel() { var elements = new List(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - BsonType bsonType; - while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument) + bsonReader.ReadStartDocument(); + BsonType bsonType; + while ((bsonType = bsonReader.ReadBsonType()) != BsonType.EndOfDocument) + { + var name = bsonReader.ReadName(); + BsonValue value; + switch (bsonType) { - var name = bsonReader.ReadName(); - BsonValue value; - switch (bsonType) - { - case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break; - case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break; - default: value = BsonValueSerializer.Instance.Deserialize(context); break; - } - elements.Add(new BsonElement(name, value)); + case BsonType.Array: value = DeserializeLazyBsonArray(bsonReader); break; + case BsonType.Document: value = DeserializeLazyBsonDocument(bsonReader); break; + default: value = BsonValueSerializer.Instance.Deserialize(context); break; } - bsonReader.ReadEndDocument(); + elements.Add(new BsonElement(name, value)); } + bsonReader.ReadEndDocument(); return elements; } diff --git a/src/MongoDB.Bson/ObjectModel/ObjectId.cs b/src/MongoDB.Bson/ObjectModel/ObjectId.cs index 6cebdff4f7e..2efbc92f9cb 100644 --- a/src/MongoDB.Bson/ObjectModel/ObjectId.cs +++ b/src/MongoDB.Bson/ObjectModel/ObjectId.cs @@ -44,14 +44,14 @@ public ObjectId(byte[] bytes) { if (bytes == null) { - throw new ArgumentNullException("bytes"); + throw new ArgumentNullException(nameof(bytes)); } if (bytes.Length != 12) { - throw new ArgumentException("Byte array must be 12 bytes long", "bytes"); + throw new ArgumentException("Byte array must be 12 bytes long", nameof(bytes)); } - FromByteArray(bytes, 0, out _a, out _b, out _c); + FromByteArray(bytes, out _a, out _b, out _c); } /// @@ -61,7 +61,12 @@ public ObjectId(byte[] bytes) /// The index into the byte array where the ObjectId starts. internal ObjectId(byte[] bytes, int index) { - FromByteArray(bytes, index, out _a, out _b, out _c); + FromByteArray(new ReadOnlySpan(bytes, index, 12), out _a, out _b, out _c); + } + + internal ObjectId(ReadOnlySpan span) + { + FromByteArray(span, out _a, out _b, out _c); } /// @@ -72,11 +77,11 @@ public ObjectId(string value) { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } var bytes = BsonUtils.ParseHexString(value); - FromByteArray(bytes, 0, out _a, out _b, out _c); + FromByteArray(bytes, out _a, out _b, out _c); } private ObjectId(int a, int b, int c) @@ -338,16 +343,16 @@ private static int GetTimestampFromDateTime(DateTime timestamp) var secondsSinceEpoch = (long)Math.Floor((BsonUtils.ToUniversalTime(timestamp) - BsonConstants.UnixEpoch).TotalSeconds); if (secondsSinceEpoch < uint.MinValue || secondsSinceEpoch > uint.MaxValue) { - throw new ArgumentOutOfRangeException("timestamp"); + throw new ArgumentOutOfRangeException(nameof(timestamp)); } return (int)(uint)secondsSinceEpoch; } - private static void FromByteArray(byte[] bytes, int offset, out int a, out int b, out int c) + private static void FromByteArray(ReadOnlySpan span, out int a, out int b, out int c) { - a = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]; - b = (bytes[offset + 4] << 24) | (bytes[offset + 5] << 16) | (bytes[offset + 6] << 8) | bytes[offset + 7]; - c = (bytes[offset + 8] << 24) | (bytes[offset + 9] << 16) | (bytes[offset + 10] << 8) | bytes[offset + 11]; + a = (span[0] << 24) | (span[1] << 16) | (span[2] << 8) | span[3]; + b = (span[4] << 24) | (span[5] << 16) | (span[6] << 8) | span[7]; + c = (span[8] << 24) | (span[9] << 16) | (span[10] << 8) | span[11]; } // public methods diff --git a/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs b/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs index 9ddbd6fcb57..3bfb2573057 100644 --- a/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs +++ b/src/MongoDB.Bson/ObjectModel/RawBsonArray.cs @@ -16,7 +16,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; @@ -34,8 +33,8 @@ public class RawBsonArray : BsonArray, IDisposable // private fields private bool _disposed; private IByteBuffer _slice; - private List _disposableItems = new List(); - private BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults; + private List _disposableItems = new(); + private readonly BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults; // constructors /// @@ -79,22 +78,20 @@ public override int Count get { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var count = 0; + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - bsonReader.SkipName(); - bsonReader.SkipValue(); - count++; - } - bsonReader.ReadEndDocument(); + var count = 0; - return count; + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + bsonReader.SkipValue(); + count++; } + bsonReader.ReadEndDocument(); + + return count; } } @@ -125,19 +122,16 @@ public override IEnumerable Values get { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - bsonReader.SkipName(); - yield return DeserializeBsonValue(context); - } - bsonReader.ReadEndDocument(); + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + yield return DeserializeBsonValue(context); } + bsonReader.ReadEndDocument(); } } @@ -153,31 +147,29 @@ public override BsonValue this[int index] { if (index < 0) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); } ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + + bsonReader.ReadStartDocument(); + var i = 0; + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { - bsonReader.ReadStartDocument(); - var i = 0; - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + bsonReader.SkipName(); + if (i == index) { - bsonReader.SkipName(); - if (i == index) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); - return DeserializeBsonValue(context); - } - - bsonReader.SkipValue(); - i++; + var context = BsonDeserializationContext.CreateRoot(bsonReader); + return DeserializeBsonValue(context); } - bsonReader.ReadEndDocument(); - throw new ArgumentOutOfRangeException("index"); + bsonReader.SkipValue(); + i++; } + bsonReader.ReadEndDocument(); + + throw new ArgumentOutOfRangeException(nameof(index)); } set { @@ -311,24 +303,22 @@ public override void Clear() public override bool Contains(BsonValue value) { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); + + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + if (DeserializeBsonValue(context).Equals(value)) { - bsonReader.SkipName(); - if (DeserializeBsonValue(context).Equals(value)) - { - return true; - } + return true; } - bsonReader.ReadEndDocument(); - - return false; } + bsonReader.ReadEndDocument(); + + return false; } /// @@ -339,19 +329,17 @@ public override bool Contains(BsonValue value) public override void CopyTo(BsonValue[] array, int arrayIndex) { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - bsonReader.SkipName(); - array[arrayIndex++] = DeserializeBsonValue(context); - } - bsonReader.ReadEndDocument(); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); + + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + array[arrayIndex++] = DeserializeBsonValue(context); } + bsonReader.ReadEndDocument(); } /// @@ -380,19 +368,17 @@ public void Dispose() public override IEnumerator GetEnumerator() { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - bsonReader.SkipName(); - yield return DeserializeBsonValue(context); - } - bsonReader.ReadEndDocument(); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); + + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + yield return DeserializeBsonValue(context); } + bsonReader.ReadEndDocument(); } /// @@ -426,41 +412,39 @@ public override int IndexOf(BsonValue value, int index) public override int IndexOf(BsonValue value, int index, int count) { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - var i = 0; - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); + + bsonReader.ReadStartDocument(); + var i = 0; + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + if (i >= index) { - bsonReader.SkipName(); - if (i >= index) + if (count == 0) { - if (count == 0) - { - return -1; - } - - if (DeserializeBsonValue(context).Equals(value)) - { - return i; - } - - count--; + return -1; } - else + + if (DeserializeBsonValue(context).Equals(value)) { - bsonReader.SkipValue(); + return i; } - i++; + count--; + } + else + { + bsonReader.SkipValue(); } - bsonReader.ReadEndDocument(); - return -1; + i++; } + bsonReader.ReadEndDocument(); + + return -1; } /// @@ -486,13 +470,10 @@ public BsonArray Materialize(BsonBinaryReaderSettings binaryReaderSettings) var document = new BsonDocument("array", this); var bytes = document.ToBson(); - using (var stream = new MemoryStream(bytes)) - using (var reader = new BsonBinaryReader(stream, binaryReaderSettings)) - { - var context = BsonDeserializationContext.CreateRoot(reader); - var materializedDocument = BsonDocumentSerializer.Instance.Deserialize(context); - return materializedDocument["array"].AsBsonArray; - } + using var reader = new ReadOnlyMemoryBsonReader(bytes, new(binaryReaderSettings)); + var context = BsonDeserializationContext.CreateRoot(reader); + var materializedDocument = BsonDocumentSerializer.Instance.Deserialize(context); + return materializedDocument["array"].AsBsonArray; } /// diff --git a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs index 6fde27bf5c3..979c4afc8e2 100644 --- a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs @@ -16,8 +16,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Linq; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; @@ -64,77 +62,65 @@ public RawBsonDocument(byte[] bytes) } // public properties - /// - /// Gets the number of elements. - /// + /// public override int ElementCount { get { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var elementCount = 0; + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - bsonReader.SkipName(); - bsonReader.SkipValue(); - elementCount++; - } - bsonReader.ReadEndDocument(); + var elementCount = 0; - return elementCount; + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + bsonReader.SkipValue(); + elementCount++; } + bsonReader.ReadEndDocument(); + + return elementCount; } } - /// - /// Gets the elements. - /// + /// public override IEnumerable Elements { get { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - var name = bsonReader.ReadName(); - var value = DeserializeBsonValue(context); - yield return new BsonElement(name, value); - } - bsonReader.ReadEndDocument(); + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + var name = bsonReader.ReadName(); + var value = DeserializeBsonValue(context); + yield return new BsonElement(name, value); } + bsonReader.ReadEndDocument(); } } - /// - /// Gets the element names. - /// + /// public override IEnumerable Names { get { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - yield return bsonReader.ReadName(); - bsonReader.SkipValue(); - } - bsonReader.ReadEndDocument(); + yield return bsonReader.ReadName(); + bsonReader.SkipValue(); } + bsonReader.ReadEndDocument(); } } @@ -153,47 +139,34 @@ public IByteBuffer Slice } } - /// - /// Gets the values. - /// + /// public override IEnumerable Values { get { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - bsonReader.SkipName(); - yield return DeserializeBsonValue(context); - } - bsonReader.ReadEndDocument(); + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + yield return DeserializeBsonValue(context); } + bsonReader.ReadEndDocument(); } } // public indexers - /// - /// Gets or sets a value by position. - /// - /// The position. - /// The value. + /// public override BsonValue this[int index] { get { return GetValue(index); } set { Set(index, value); } } - /// - /// Gets or sets a value by name. - /// - /// The name. - /// The value. + /// public override BsonValue this[string name] { get { return GetValue(name); } @@ -201,243 +174,154 @@ public override BsonValue this[string name] } // public methods - /// - /// Adds an element to the document. - /// - /// The element to add. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument Add(BsonElement element) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Creates and adds an element to the document. - /// - /// The name of the element. - /// The value of the element. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument Add(string name, BsonValue value) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Creates and adds an element to the document, but only if the condition is true. - /// - /// The name of the element. - /// The value of the element. - /// Whether to add the element to the document. - /// The document (so method calls can be chained). + /// public override BsonDocument Add(string name, BsonValue value, bool condition) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Adds elements to the document from a dictionary of key/value pairs. - /// - /// The dictionary. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument AddRange(Dictionary dictionary) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Adds elements to the document from a dictionary of key/value pairs. - /// - /// The dictionary. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument AddRange(IDictionary dictionary) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Adds a list of elements to the document. - /// - /// The list of elements. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument AddRange(IEnumerable elements) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Adds elements to the document from a dictionary of key/value pairs. - /// - /// The dictionary. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument AddRange(IEnumerable> dictionary) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Clears the document (removes all elements). - /// + /// public override void Clear() { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Creates a shallow clone of the document (see also DeepClone). - /// - /// - /// A shallow clone of the document. - /// + /// public override BsonValue Clone() { ThrowIfDisposed(); return new RawBsonDocument(CloneSlice()); } - /// - /// Tests whether the document contains an element with the specified name. - /// - /// The name of the element to look for. - /// - /// True if the document contains an element with the specified name. - /// + /// public override bool Contains(string name) { if (name == null) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + if (bsonReader.ReadName() == name) { - if (bsonReader.ReadName() == name) - { - return true; - } - bsonReader.SkipValue(); + return true; } - bsonReader.ReadEndDocument(); - - return false; + bsonReader.SkipValue(); } + bsonReader.ReadEndDocument(); + + return false; } - /// - /// Tests whether the document contains an element with the specified value. - /// - /// The value of the element to look for. - /// - /// True if the document contains an element with the specified value. - /// + /// public override bool ContainsValue(BsonValue value) { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + if (DeserializeBsonValue(context).Equals(value)) { - bsonReader.SkipName(); - if (DeserializeBsonValue(context).Equals(value)) - { - return true; - } + return true; } - bsonReader.ReadEndDocument(); - - return false; } + bsonReader.ReadEndDocument(); + + return false; } - /// - /// Creates a deep clone of the document (see also Clone). - /// - /// - /// A deep clone of the document. - /// + /// public override BsonValue DeepClone() { ThrowIfDisposed(); return new RawBsonDocument(CloneSlice()); } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// + /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - /// - /// Gets an element of this document. - /// - /// The zero based index of the element. - /// - /// The element. - /// + /// public override BsonElement GetElement(int index) { if (index < 0) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); } ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - var i = 0; - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + bsonReader.ReadStartDocument(); + var i = 0; + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + if (i == index) { - if (i == index) - { - var name = bsonReader.ReadName(); - var value = DeserializeBsonValue(context); - return new BsonElement(name, value); - } - - bsonReader.SkipName(); - bsonReader.SkipValue(); - i++; + var name = bsonReader.ReadName(); + var value = DeserializeBsonValue(context); + return new BsonElement(name, value); } - bsonReader.ReadEndDocument(); - throw new ArgumentOutOfRangeException("index"); + bsonReader.SkipName(); + bsonReader.SkipValue(); + i++; } + bsonReader.ReadEndDocument(); + + throw new ArgumentOutOfRangeException(nameof(index)); } - /// - /// Gets an element of this document. - /// - /// The name of the element. - /// - /// A BsonElement. - /// + /// public override BsonElement GetElement(string name) { ThrowIfDisposed(); @@ -447,81 +331,56 @@ public override BsonElement GetElement(string name) return element; } - string message = string.Format("Element '{0}' not found.", name); - throw new KeyNotFoundException(message); + throw new KeyNotFoundException($"Element '{name}' not found."); } - /// - /// Gets an enumerator that can be used to enumerate the elements of this document. - /// - /// - /// An enumerator. - /// + /// public override IEnumerator GetEnumerator() { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - var name = bsonReader.ReadName(); - var value = DeserializeBsonValue(context); - yield return new BsonElement(name, value); - } - bsonReader.ReadEndDocument(); + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + var name = bsonReader.ReadName(); + var value = DeserializeBsonValue(context); + yield return new BsonElement(name, value); } } - /// - /// Gets the value of an element. - /// - /// The zero based index of the element. - /// - /// The value of the element. - /// + /// public override BsonValue GetValue(int index) { if (index < 0) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); } ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - var i = 0; - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + bsonReader.ReadStartDocument(); + var i = 0; + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + bsonReader.SkipName(); + if (i == index) { - bsonReader.SkipName(); - if (i == index) - { - return DeserializeBsonValue(context); - } - - bsonReader.SkipValue(); - i++; + return DeserializeBsonValue(context); } - bsonReader.ReadEndDocument(); - throw new ArgumentOutOfRangeException("index"); + bsonReader.SkipValue(); + i++; } + bsonReader.ReadEndDocument(); + + throw new ArgumentOutOfRangeException(nameof(index)); } - /// - /// Gets the value of an element. - /// - /// The name of the element. - /// - /// The value of the element. - /// + /// public override BsonValue GetValue(string name) { ThrowIfDisposed(); @@ -535,14 +394,7 @@ public override BsonValue GetValue(string name) throw new KeyNotFoundException(message); } - /// - /// Gets the value of an element or a default value if the element is not found. - /// - /// The name of the element. - /// The default value returned if the element is not found. - /// - /// The value of the element or the default value if the element is not found. - /// + /// public override BsonValue GetValue(string name, BsonValue defaultValue) { ThrowIfDisposed(); @@ -555,11 +407,7 @@ public override BsonValue GetValue(string name, BsonValue defaultValue) return defaultValue; } - /// - /// Inserts a new element at a specified position. - /// - /// The position of the new element. - /// The element. + /// public override void InsertAt(int index, BsonElement element) { throw new NotSupportedException("RawBsonDocument instances are immutable."); @@ -573,185 +421,115 @@ public override void InsertAt(int index, BsonElement element) public BsonDocument Materialize(BsonBinaryReaderSettings binaryReaderSettings) { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var reader = new BsonBinaryReader(stream, binaryReaderSettings)) - { - var context = BsonDeserializationContext.CreateRoot(reader); - return BsonDocumentSerializer.Instance.Deserialize(context); - } + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, binaryReaderSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); + + return BsonDocumentSerializer.Instance.Deserialize(context); } - /// - /// Merges another document into this one. Existing elements are not overwritten. - /// - /// The other document. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument Merge(BsonDocument document) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Merges another document into this one, specifying whether existing elements are overwritten. - /// - /// The other document. - /// Whether to overwrite existing elements. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument Merge(BsonDocument document, bool overwriteExistingElements) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Removes an element from this document (if duplicate element names are allowed - /// then all elements with this name will be removed). - /// - /// The name of the element to remove. + /// public override void Remove(string name) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Removes an element from this document. - /// - /// The zero based index of the element to remove. + /// public override void RemoveAt(int index) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Removes an element from this document. - /// - /// The element to remove. + /// public override void RemoveElement(BsonElement element) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Sets the value of an element. - /// - /// The zero based index of the element whose value is to be set. - /// The new value. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument Set(int index, BsonValue value) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Sets the value of an element (an element will be added if no element with this name is found). - /// - /// The name of the element whose value is to be set. - /// The new value. - /// - /// The document (so method calls can be chained). - /// + /// public override BsonDocument Set(string name, BsonValue value) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Sets an element of the document (replaces any existing element with the same name or adds a new element if an element with the same name is not found). - /// - /// The new element. - /// - /// The document. - /// + /// public override BsonDocument SetElement(BsonElement element) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Sets an element of the document (replacing the existing element at that position). - /// - /// The zero based index of the element to replace. - /// The new element. - /// - /// The document. - /// + /// public override BsonDocument SetElement(int index, BsonElement element) { throw new NotSupportedException("RawBsonDocument instances are immutable."); } - /// - /// Tries to get an element of this document. - /// - /// The name of the element. - /// The element. - /// - /// True if an element with that name was found. - /// + /// public override bool TryGetElement(string name, out BsonElement element) { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - if (bsonReader.ReadName() == name) - { - var value = DeserializeBsonValue(context); - element = new BsonElement(name, value); - return true; - } + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.SkipValue(); + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + if (bsonReader.ReadName() == name) + { + var value = DeserializeBsonValue(context); + element = new BsonElement(name, value); + return true; } - bsonReader.ReadEndDocument(); - element = default(BsonElement); - return false; + bsonReader.SkipValue(); } + bsonReader.ReadEndDocument(); + + element = default; + return false; } - /// - /// Tries to get the value of an element of this document. - /// - /// The name of the element. - /// The value of the element. - /// - /// True if an element with that name was found. - /// + /// public override bool TryGetValue(string name, out BsonValue value) { ThrowIfDisposed(); - using (var stream = new ByteBufferStream(_slice, ownsBuffer: false)) - using (var bsonReader = new BsonBinaryReader(stream, _readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.ReadStartDocument(); - while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) - { - if (bsonReader.ReadName() == name) - { - value = DeserializeBsonValue(context); - return true; - } + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); + var context = BsonDeserializationContext.CreateRoot(bsonReader); - bsonReader.SkipValue(); + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + if (bsonReader.ReadName() == name) + { + value = DeserializeBsonValue(context); + return true; } - bsonReader.ReadEndDocument(); - value = null; - return false; + bsonReader.SkipValue(); } + bsonReader.ReadEndDocument(); + + value = null; + return false; } // protected methods diff --git a/src/MongoDB.Driver/Core/Misc/Ensure.cs b/src/MongoDB.Driver/Core/Misc/Ensure.cs index aff914d07fe..7ff08b3283b 100644 --- a/src/MongoDB.Driver/Core/Misc/Ensure.cs +++ b/src/MongoDB.Driver/Core/Misc/Ensure.cs @@ -83,7 +83,7 @@ public static T IsEqualTo(T value, T comparand, string paramName) /// /// Ensures that the value of a parameter is greater than a comparand. /// - /// Type type of the value. + /// Type of the value. /// The value of the parameter. /// The comparand. /// The name of the parameter. diff --git a/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs b/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs index f43cb30a168..7f3399aa20c 100644 --- a/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs +++ b/src/MongoDB.Driver/Core/Operations/ChangeStreamCursor.cs @@ -159,15 +159,12 @@ public BsonDocument GetResumeToken() } // private methods - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] private TDocument DeserializeDocument(RawBsonDocument rawDocument) { - using (var stream = new ByteBufferStream(rawDocument.Slice, ownsBuffer: false)) - using (var reader = new BsonBinaryReader(stream)) - { - var context = BsonDeserializationContext.CreateRoot(reader); - return _documentSerializer.Deserialize(context); - } + using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(rawDocument.Slice, BsonBinaryReaderSettings.Defaults); + var context = BsonDeserializationContext.CreateRoot(bsonReader); + + return _documentSerializer.Deserialize(context); } private IEnumerable DeserializeDocuments(IEnumerable rawDocuments) diff --git a/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs b/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs index 27ff4252dc0..8a2883ad5ca 100644 --- a/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs +++ b/src/MongoDB.Driver/Core/Operations/CursorBatchDeserializationHelper.cs @@ -1,4 +1,4 @@ -/* Copyright 2015-present MongoDB Inc. +/* Copyright 2010-present MongoDB Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,6 @@ internal static class CursorBatchDeserializationHelper /// The document serializer. /// The message encoder settings. /// The documents. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] public static List DeserializeBatch(RawBsonArray batch, IBsonSerializer documentSerializer, MessageEncoderSettings messageEncoderSettings) { var documents = new List(); @@ -44,23 +43,20 @@ public static List DeserializeBatch(RawBsonArray batch, IB if (messageEncoderSettings != null) { readerSettings.Encoding = messageEncoderSettings.GetOrDefault(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict); - }; + } - using (var stream = new ByteBufferStream(batch.Slice, ownsBuffer: false)) - using (var reader = new BsonBinaryReader(stream, readerSettings)) - { - var context = BsonDeserializationContext.CreateRoot(reader); + using var reader = BsonBinaryReaderUtils.CreateBinaryReader(batch.Slice, readerSettings); + var context = BsonDeserializationContext.CreateRoot(reader); - // BSON requires that the top level object be a document, but an array looks close enough to a document that we can pretend it is one - reader.ReadStartDocument(); - while (reader.ReadBsonType() != 0) - { - reader.SkipName(); // skip over the index pseudo names - var document = documentSerializer.Deserialize(context); - documents.Add(document); - } - reader.ReadEndDocument(); + // BSON requires that the top level object be a document, but an array looks close enough to a document that we can pretend it is one + reader.ReadStartDocument(); + while (reader.ReadBsonType() != 0) + { + reader.SkipName(); // skip over the index pseudo names + var document = documentSerializer.Deserialize(context); + documents.Add(document); } + reader.ReadEndDocument(); return documents; } diff --git a/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs b/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs index 1b346fe013f..5b8ac3eaba0 100644 --- a/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs +++ b/src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs @@ -14,7 +14,6 @@ */ using System; -using System.Text; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Bson.IO; @@ -125,10 +124,9 @@ public TResult ExecuteAttempt(OperationContext operationContext, RetryableWriteC using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel, binding.Session.Fork())) { var operation = CreateOperation(operationContext, channelBinding.Session, channel.ConnectionDescription, transactionNumber); - using (var rawBsonDocument = operation.Execute(operationContext, channelBinding)) - { - return ProcessCommandResult(channel.ConnectionDescription.ConnectionId, rawBsonDocument); - } + using var rawBsonDocument = operation.Execute(operationContext, channelBinding); + + return ProcessCommandResult(rawBsonDocument); } } @@ -141,10 +139,9 @@ public async Task ExecuteAttemptAsync(OperationContext operationContext using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel, binding.Session.Fork())) { var operation = CreateOperation(operationContext, channelBinding.Session, channel.ConnectionDescription, transactionNumber); - using (var rawBsonDocument = await operation.ExecuteAsync(operationContext, channelBinding).ConfigureAwait(false)) - { - return ProcessCommandResult(channel.ConnectionDescription.ConnectionId, rawBsonDocument); - } + using var rawBsonDocument = await operation.ExecuteAsync(operationContext, channelBinding).ConfigureAwait(false); + + return ProcessCommandResult(rawBsonDocument); } } @@ -163,20 +160,16 @@ private WriteCommandOperation CreateOperation(OperationContext }; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - private TResult ProcessCommandResult(ConnectionId connectionId, RawBsonDocument rawBsonDocument) + private TResult ProcessCommandResult(RawBsonDocument rawBsonDocument) { var binaryReaderSettings = new BsonBinaryReaderSettings { - Encoding = _messageEncoderSettings.GetOrDefault(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict) + Encoding = _messageEncoderSettings.GetOrDefault(MessageEncoderSettingsName.ReadEncoding, Utf8Encodings.Strict) }; - using (var stream = new ByteBufferStream(rawBsonDocument.Slice, ownsBuffer: false)) - using (var reader = new BsonBinaryReader(stream, binaryReaderSettings)) - { - var context = BsonDeserializationContext.CreateRoot(reader); - return _resultSerializer.Deserialize(context); - } + using var reader = BsonBinaryReaderUtils.CreateBinaryReader(rawBsonDocument.Slice, binaryReaderSettings); + var context = BsonDeserializationContext.CreateRoot(reader); + return _resultSerializer.Deserialize(context); } } } diff --git a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs index 4b7af6b7209..288f0007076 100644 --- a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs +++ b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs @@ -440,7 +440,6 @@ private void MessageWasProbablySent(CommandRequestMessage message) } } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] private TCommandResult ProcessResponse(ConnectionId connectionId, CommandMessage responseMessage) { using (new CommandMessageDisposer(responseMessage)) @@ -532,14 +531,9 @@ private TCommandResult ProcessResponse(ConnectionId connectionId, CommandMessage throw new MongoWriteConcernException(connectionId, message, writeConcernResult); } - using (var stream = new ByteBufferStream(rawDocument.Slice, ownsBuffer: false)) - { - using (var reader = new BsonBinaryReader(stream, binaryReaderSettings)) - { - var context = BsonDeserializationContext.CreateRoot(reader); - return _resultSerializer.Deserialize(context); - } - } + using var reader = BsonBinaryReaderUtils.CreateBinaryReader(rawDocument.Slice, binaryReaderSettings); + var context = BsonDeserializationContext.CreateRoot(reader); + return _resultSerializer.Deserialize(context); } } diff --git a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs index ecfb53f0f70..f625704e6dc 100644 --- a/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs +++ b/src/MongoDB.Driver/Core/WireProtocol/CommandUsingQueryMessageWireProtocol.cs @@ -235,7 +235,6 @@ private void IgnoreResponse(OperationContext operationContext, IConnection conne connection.ReceiveMessageAsync(operationContext, message.RequestId, encoderSelector, _messageEncoderSettings).IgnoreExceptions(); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] private TCommandResult ProcessReply(ConnectionId connectionId, ReplyMessage reply) { if (reply.NumberReturned == 0) diff --git a/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryBufferTests.cs b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryBufferTests.cs new file mode 100644 index 00000000000..d98bd358265 --- /dev/null +++ b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryBufferTests.cs @@ -0,0 +1,362 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Linq; +using FluentAssertions; +using MongoDB.Bson.IO; +using MongoDB.TestHelpers.XunitExtensions; +using Xunit; + +namespace MongoDB.Bson.Tests.IO; + +public class ReadOnlyMemoryBufferTests +{ + [Theory] + [InlineData(0, 0)] + [InlineData(1, 1)] + [InlineData(2, 2)] + public void AccessBackingBytes_should_return_expected_result_for_length(int length, int expectedCount) + { + var bytes = Enumerable.Range(0, length).Select(i => (byte)i).ToArray(); + var subject = CreateSubject(bytes); + + var result = subject.AccessBackingBytes(0); + + result.Array.Should().BeEquivalentTo(bytes); + result.Offset.Should().Be(0); + result.Count.Should().Be(expectedCount); + } + + [Theory] + [InlineData(0, 2)] + [InlineData(1, 1)] + [InlineData(2, 0)] + public void AccessBackingBytes_should_return_expected_result_for_position(int position, int expectedCount) + { + var bytes = new byte[2]; + var subject = CreateSubject(bytes); + + var result = subject.AccessBackingBytes(position); + + result.Array.Should().BeEquivalentTo(bytes.Skip(position).ToArray()); + result.Offset.Should().Be(0); + result.Count.Should().Be(expectedCount); + } + + [Theory] + [ParameterAttributeData] + public void AccessBackingBytes_should_throw_when_position_is_invalid([Values(-1, 3)]int position) + { + var subject = CreateSubject(2); + + Action action = () => subject.AccessBackingBytes(position); + + action.ShouldThrow(); + } + + [Theory] + [InlineData(2)] + [InlineData(4)] + public void Capacity_get_should_return_expected_result(int length) + { + var subject = CreateSubject(length); + + var result = subject.Capacity; + + result.Should().Be(length); + } + + [Fact] + public void Clear_should_throw() + { + var bytes = new byte[] { 1, 2 }; + var subject = CreateSubject(bytes); + + var e = Record.Exception(() => subject.Clear(0, 0)); + e.Should().BeOfType(); + e.Message.Should().Contain("is not writable."); + } + + [Theory] + [ParameterAttributeData] + public void constructor_should_initialize_subject([Values(1, 2)]int length) + { + var bytes = Enumerable.Range(0, length).Select(i => (byte)i).ToArray(); + var subject = CreateSubject(bytes); + + subject.IsReadOnly.Should().Be(true); + subject.Length.Should().Be(length); + subject.Memory.ToArray().ShouldBeEquivalentTo(bytes); + } + + [Fact] + public void Dispose_can_be_called_multiple_times() + { + var subject = CreateSubject(2); + + subject.Dispose(); + subject.Dispose(); + } + + [Fact] + public void EnsureCapacity_should_throw() + { + var subject = CreateSubject(2); + + ValidateWritableException(() => subject.EnsureCapacity(1)); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(2, 3)] + public void GetByte_should_return_expected_result(int position, byte expectedResult) + { + var bytes = new byte[] { 1, 2, 3 }; + var subject = CreateSubject(bytes); + + var result = subject.GetByte(position); + + result.Should().Be(expectedResult); + } + + [Theory] + [ParameterAttributeData] + public void GetByte_should_throw_when_position_is_invalid( + [Values(-1, 3)] + int position) + { + var subject = CreateSubject(2); + + Action action = () => subject.GetByte(position); + + action.ShouldThrow(); + } + + [Theory] + [InlineData(0, new byte[] { 0, 0 })] + [InlineData(1, new byte[] { 1, 0 })] + [InlineData(2, new byte[] { 1, 2 })] + public void GetBytes_should_have_expected_effect_for_count(int count, byte[] expectedBytes) + { + var bytes = new byte[] { 1, 2 }; + var subject = CreateSubject(bytes); + var destination = new byte[2]; + + subject.GetBytes(0, destination, 0, count); + + destination.Should().Equal(expectedBytes); + } + + [Theory] + [InlineData(1, new byte[] { 0, 1, 2, 0 })] + [InlineData(2, new byte[] { 0, 0, 1, 2 })] + public void GetBytes_should_have_expected_effect_for_offset(int offset, byte[] expectedBytes) + { + var bytes = new byte[] { 1, 2 }; + var subject = CreateSubject(bytes); + var destination = new byte[4]; + + subject.GetBytes(0, destination, offset, 2); + + destination.Should().Equal(expectedBytes); + } + + [Theory] + [InlineData(1, new byte[] { 2, 3 })] + [InlineData(2, new byte[] { 3, 4 })] + public void GetBytes_should_have_expected_effect_for_position(int position, byte[] expectedBytes) + { + var bytes = new byte[] { 1, 2, 3, 4 }; + var subject = CreateSubject(bytes); + var destination = new byte[2]; + + subject.GetBytes(position, destination, 0, 2); + destination.Should().Equal(expectedBytes); + } + + [Theory] + [InlineData(0, -1)] + [InlineData(0, 3)] + [InlineData(1, 2)] + [InlineData(2, 1)] + public void GetBytes_should_throw_when_count_is_invalid_for_buffer(int position, int count) + { + var subject = CreateSubject(2); + var destination = new byte[3]; + + Action action = () => subject.GetBytes(position, destination, 0, count); + + action.ShouldThrow(); + } + + [Theory] + [InlineData(0, -1)] + [InlineData(0, 3)] + [InlineData(1, 2)] + [InlineData(2, 1)] + public void GetBytes_should_throw_when_count_is_invalid_for_destination(int offset, int count) + { + var subject = CreateSubject([1, 2, 3]); + var destination = new byte[2]; + + Action action = () => subject.GetBytes(0, destination, offset, count); + + action.ShouldThrow(); + } + + [Fact] + public void GetBytes_should_throw_when_destination_is_null() + { + var subject = CreateSubject([1, 2]); + + Action action = () => subject.GetBytes(0, null, 0, 2); + + action.ShouldThrow(); + } + + [Theory] + [ParameterAttributeData] + public void GetBytes_should_throw_when_offset_is_invalid([Values(-1, 3)]int offset) + { + var subject = CreateSubject([1, 2, 3, 4]); + var destination = new byte[2]; + + Action action = () => subject.GetBytes(0, destination, offset, 0); + + action.ShouldThrow(); + } + + [Theory] + [ParameterAttributeData] + public void GetBytes_should_throw_when_position_is_invalid( + [Values(-1, 3)] + int position) + { + var subject = CreateSubject([1, 2]); + var destination = new byte[0]; + + Action action = () => subject.GetBytes(position, destination, 0, 0); + + action.ShouldThrow(); + } + + [Theory] + [InlineData(0, 2)] + [InlineData(1, 1)] + [InlineData(2, 0)] + public void GetSlice_should_return_expected_result(int position, int length) + { + var bytes = new byte[2]; + var subject = CreateSubject(bytes); + + var result = subject.GetSlice(position, length); + + result.AccessBackingBytes(0).Offset.Should().Be(0); + result.AccessBackingBytes(0).Count.Should().Be(length); + } + + [Fact] + public void GetSlice_should_return_slice_that_does_not_dispose_subject_when_slice_is_disposed() + { + var bytes = new byte[] { 1, 2, 3 }; + var subject = CreateSubject(bytes); + var slice = subject.GetSlice(1, 1); + + slice.Dispose(); + + subject.GetByte(1).Should().Be(2); + } + + [Fact] + public void GetSlice_should_return_slice_that_is_not_disposed_when_subject_is_disposed() + { + var bytes = new byte[] { 1, 2, 3 }; + var subject = CreateSubject(bytes); + var slice = subject.GetSlice(1, 1); + + subject.Dispose(); + + slice.GetByte(0).Should().Be(2); + } + + [Theory] + [InlineData(0, -1)] + [InlineData(0, 3)] + [InlineData(1, 2)] + [InlineData(2, 1)] + public void GetSlice_should_throw_when_length_is_invalid(int position, int length) + { + var subject = CreateSubject(2); + + Action action = () => subject.GetSlice(position, length); + + action.ShouldThrow(); + } + + [Theory] + [ParameterAttributeData] + public void GetSlice_should_throw_when_position_is_invalid([Values(-1, 3)]int position) + { + var subject = CreateSubject(2); + + Action action = () => subject.GetSlice(position, 0); + + action.ShouldThrow(); + } + + [Fact] + public void Length_set_should_throw() + { + var subject = CreateDisposedSubject(); + + ValidateWritableException(() => subject.Length = 0); + } + + [Fact] + public void MakeReadOnly_should_do_nothing() + { + var subject = CreateSubject(2); + subject.MakeReadOnly(); + } + + [Fact] + public void SetBytes_should_throw() + { + var subject = CreateSubject(2); + var source = new byte[0]; + + ValidateWritableException(() => subject.SetBytes(0, source, 0, 0)); + } + + // helper methods + private ReadOnlyMemoryBuffer CreateDisposedSubject() + { + var subject = CreateSubject(2); + subject.Dispose(); + return subject; + } + + private ReadOnlyMemoryBuffer CreateSubject(int length) => new(Enumerable.Range(0, length).Select(i => (byte)i).ToArray()); + + private ReadOnlyMemoryBuffer CreateSubject(byte[] bytes) => new(bytes); + + private void ValidateWritableException(Action action) + { + var e = Record.Exception(action); + e.Should().BeOfType(); + e.Message.Should().Contain("is not writable."); + } +} diff --git a/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryReaderTests.cs b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryReaderTests.cs new file mode 100644 index 00000000000..b5528b08226 --- /dev/null +++ b/tests/MongoDB.Bson.Tests/IO/ReadOnlyMemoryReaderTests.cs @@ -0,0 +1,355 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; +using MongoDB.TestHelpers.XunitExtensions; +using Xunit; + +namespace MongoDB.Bson.Tests.IO; + +public class ReadOnlyMemoryReaderTests +{ + [Fact] + public void Bookmarks_should_work() + { + var document = new BsonDocument { { "x", 1 }, { "y", 2 }, { "z", new BsonDocument { { "a", "a"}, { "b", "b" } } } }; + var bytes = document.ToBson(); + + using var bsonReader = new ReadOnlyMemoryBsonReader(bytes); + + AssertRead(() => bsonReader.ReadBsonType(), BsonType.Document); + DoReaderAction(() => bsonReader.ReadStartDocument()); + + var bookmarkX = bsonReader.GetBookmark(); + AssertRead(() => bsonReader.ReadBsonType(), BsonType.Int32); + AssertRead(() => bsonReader.ReadName(), "x"); + AssertRead(() => bsonReader.ReadInt32(), 1); + + AssertRead(() => bsonReader.ReadBsonType(), BsonType.Int32); + AssertRead(() => bsonReader.ReadName(), "y"); + AssertRead(() => bsonReader.ReadInt32(), 2); + + AssertRead(() => bsonReader.ReadBsonType(), BsonType.Document); + AssertRead(() => bsonReader.ReadName(), "z"); + DoReaderAction(() => bsonReader.ReadStartDocument()); + + AssertRead(() => bsonReader.ReadBsonType(), BsonType.String); + AssertRead(() => bsonReader.ReadName(), "a"); + AssertRead(() => bsonReader.ReadString(), "a"); + + var bookmarkB = bsonReader.GetBookmark(); + AssertRead(() => bsonReader.ReadBsonType(), BsonType.String); + AssertRead(() => bsonReader.ReadName(), "b"); + AssertRead(() => bsonReader.ReadString(), "b"); + DoReaderAction(() => bsonReader.ReadEndDocument()); + + DoReaderAction(() => bsonReader.ReadEndDocument()); + bsonReader.State.Should().Be(BsonReaderState.Initial); + bsonReader.IsAtEndOfFile().Should().BeTrue(); + + bsonReader.ReturnToBookmark(bookmarkX); + AssertRead(() => bsonReader.ReadBsonType(), BsonType.Int32); + AssertRead(() => bsonReader.ReadName(), "x"); + AssertRead(() => bsonReader.ReadInt32(), 1); + + bsonReader.ReturnToBookmark(bookmarkB); + AssertRead(() => bsonReader.ReadBsonType(), BsonType.String); + AssertRead(() => bsonReader.ReadName(), "b"); + AssertRead(() => bsonReader.ReadString(), "b"); + DoReaderAction(() => bsonReader.ReadEndDocument()); + + // do everything twice returning to bookmark in between + void DoReaderAction(Action readerAction) + { + var bookmark = bsonReader.GetBookmark(); + readerAction(); + bsonReader.ReturnToBookmark(bookmark); + readerAction(); + } + + void AssertRead(Func reader, T expected) + { + var bookmark = bsonReader.GetBookmark(); + reader().Should().Be(expected); + bsonReader.ReturnToBookmark(bookmark); + reader().Should().Be(expected); + } + } + + [Theory] + [MemberData(nameof(BsonValues))] + public void BsonValue_should_be_deserialized(BsonValue value) + { + var document = new BsonDocument("x", value); + + RehydrateAndValidate(document); + } + + public static readonly IEnumerable BsonValues = + [ + [new BsonDecimal128(1.0M)], + [new BsonDouble(1.0)], + [new BsonBoolean(true)], + [new BsonInt32(1)], + [new BsonInt64(1L)], + [new BsonString("")], + [new BsonTimestamp(2)], + [new BsonBinaryData([1, 2, 3, 4, 5, 6])], + [new BsonRegularExpression("^p")], + [new BsonObjectId(new ObjectId("4d0ce088e447ad08b4721a37"))], + [BsonSymbolTable.Lookup("name")], + [BsonNull.Value], + [BsonMaxKey.Value], + [BsonMinKey.Value], + [BsonUndefined.Value], + [new BsonJavaScript("function f() { return 1; }")], + [new BsonJavaScriptWithScope("function f() { return n; }", new BsonDocument("n", 1))], + [new BsonArray([1, "s", 5m])], + [new BsonBinaryData([1, 2, 3])], + [new BsonDocument { { "a", "1" }, { "b", 1 }, { "c", new BsonDocument { { "d", new BsonArray([1, "s", new BsonDocument("tt", 11)]) } } } }], + [new BsonDocument()] + ]; + + [Fact] + public void ComplexDocument_should_be_deserialized() + { + var dictionary = BsonValues.Select((v, i) => ($"key_{i}", v[0])).ToDictionary(p => p.Item1, p => p.Item2); + var document = new BsonDocument(dictionary); + + RehydrateAndValidate(document); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, 1)] + [InlineData(1, 10)] + [InlineData(5, 10)] + [InlineData(10, 10)] + public void Position_set_should_set_expected_position(int position, int length) + { + using var subject = new ReadOnlyMemoryBsonReader(Enumerable.Range(0, length).Select(i => (byte)i).ToArray()); + + subject.Position = position; + subject.Position.Should().Be(position); + } + + [Theory] + [InlineData(-1)] + [InlineData(int.MaxValue)] + public void Position_set_should_thrown_on_invalid_value(int position) + { + using var subject = new ReadOnlyMemoryBsonReader(new byte[] { 1, 2 }); + + var exception = Record.Exception(() => subject.Position = position); + var formatException = exception.Should().BeOfType().Subject; + + formatException.ParamName.Should().Be("value"); + } + + [Fact] + public void ReadBoolean_should_throw_on_invalid_value() + { + byte invalidBoolean = 9; + var bytes = new byte[] + { + 9, 0, 0, 0, // Length + (byte)BsonType.Boolean, // Type + (byte)'x', 0, // Name + invalidBoolean, 0 // boolean value + }; + + using var subject = new ReadOnlyMemoryBsonReader(bytes); + var exception = Record.Exception(() => BsonSerializer.Deserialize(subject)); + var formatException = exception.Should().BeOfType().Subject; + + formatException.Message.Should().Contain($"Invalid BsonBoolean value: {invalidBoolean}"); + } + + [Theory] + [InlineData(BsonBinarySubType.UuidStandard)] + [InlineData(BsonBinarySubType.UuidLegacy)] + public void ReadBinaryData_should_read_correct_subtype(BsonBinarySubType subType) + { + var bytes = new byte[] { 29, 0, 0, 0, 5, 120, 0, 16, 0, 0, 0, (byte)subType, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0 }; + + using var reader = new ReadOnlyMemoryBsonReader(bytes); + reader.ReadStartDocument(); + var type = reader.ReadBsonType(); + var name = reader.ReadName(); + var binaryData = reader.ReadBinaryData(); + var endOfDocument = reader.ReadBsonType(); + reader.ReadEndDocument(); + + name.Should().Be("x"); + type.Should().Be(BsonType.Binary); + binaryData.SubType.Should().Be(subType); + binaryData.Bytes.Should().Equal(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + endOfDocument.Should().Be(BsonType.EndOfDocument); + } + + [Theory] + [InlineData("00000000 f0 6100", "a")] + [InlineData("00000000 08 6100 00 f0 6200", "b")] + [InlineData("00000000 03 6100 00000000 f0 6200", "a.b")] + [InlineData("00000000 03 6100 00000000 08 6200 00 f0 6300", "a.c")] + [InlineData("00000000 04 6100 00000000 f0", "a.0")] + [InlineData("00000000 04 6100 00000000 08 3000 00 f0", "a.1")] + [InlineData("00000000 04 6100 00000000 03 3000 00000000 f0 6200", "a.0.b")] + [InlineData("00000000 04 6100 00000000 03 3000 00000000 08 6200 00 f0 6300", "a.0.c")] + [InlineData("00000000 04 6100 00000000 08 3000 00 03 3100 00000000 f0 6200", "a.1.b")] + [InlineData("00000000 04 6100 00000000 08 3000 00 03 3200 00000000 08 6200 00 f0 6300", "a.1.c")] + public void ReadBsonType_should_throw_when_bson_type_is_invalid(string hexBytes, string expectedElementName) + { + var bytes = BsonUtils.ParseHexString(hexBytes.Replace(" ", "")); + var expectedMessage = $"Detected unknown BSON type \"\\xf0\" for fieldname \"{expectedElementName}\". Are you using the latest driver version?"; + + using var subject = new ReadOnlyMemoryBsonReader(bytes); + + var exception = Record.Exception(() => BsonSerializer.Deserialize(subject)); + var formatException = exception.Should().BeOfType().Subject; + + formatException.Message.Should().Contain(expectedMessage); + } + + [Fact] + public void ReadBytes_should_return_expected_result() + { + byte[] bytesExpected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + byte[] bytes = [29, 0, 0, 0, 5, 120, 0, 16, 0, 0, 0, 0, ..bytesExpected, 0 ]; + + using var reader = new ReadOnlyMemoryBsonReader(bytes); + reader.ReadStartDocument(); + var type = reader.ReadBsonType(); + var name = reader.ReadName(); + var bytesActual = reader.ReadBytes(); + var endOfDocument = reader.ReadBsonType(); + reader.ReadEndDocument(); + + name.Should().Be("x"); + type.Should().Be(BsonType.Binary); + bytesActual.ShouldAllBeEquivalentTo(bytesExpected); + endOfDocument.Should().Be(BsonType.EndOfDocument); + } + + [Theory] + [ParameterAttributeData] + public void ReadOnlyMemoryBsonReader_should_support_reading_multiple_documents([Range(0, 3)]int numberOfDocuments) + { + var document = new BsonDocument("x", 1); + var bson = document.ToBson(); + var input = Enumerable.Repeat(bson, numberOfDocuments).Aggregate(Enumerable.Empty(), (a, b) => a.Concat(b)).ToArray(); + var expectedResult = Enumerable.Repeat(document, numberOfDocuments); + + using var reader = new ReadOnlyMemoryBsonReader(input); + var result = new List(); + + while (!reader.IsAtEndOfFile()) + { + reader.ReadStartDocument(); + var name = reader.ReadName(); + var value = reader.ReadInt32(); + reader.ReadEndDocument(); + + var resultDocument = new BsonDocument(name, value); + result.Add(resultDocument); + } + + result.Should().Equal(expectedResult); + } + + [Fact] + public void ReadRawBsonArray_should_return_expected_result() + { + var bsonDocument = new BsonDocument { { "_id", 1 }, { "A", new BsonArray { 1, 2 } } }; + var bson = bsonDocument.ToBson(); + + using var reader = new ReadOnlyMemoryBsonReader(bson); + using var document = BsonSerializer.Deserialize(reader); + + document.Id.Should().Be(1); + document.A.Count.Should().Be(2); + document.A[0].AsInt32.Should().Be(1); + document.A[1].AsInt32.Should().Be(2); + bson.ShouldBeEquivalentTo(document.ToBson()); + } + + [Fact] + public void ReadRawBsonDocument_should_return_expected_result() + { + var bsonDocument = new BsonDocument { { "_id", 1 }, { "A", new BsonDocument { { "x", 1 }, { "y", "2" } } } }; + var bson = bsonDocument.ToBson(); + + using var reader = new ReadOnlyMemoryBsonReader(bson); + using var document = BsonSerializer.Deserialize(reader); + + document.Id.Should().Be(1); + document.A.Values.Count().Should().Be(2); + document.A["x"].AsInt32.Should().Be(1); + document.A["y"].AsString.Should().Be("2"); + bson.ShouldBeEquivalentTo(document.ToBson()); + } + + private void RehydrateAndValidate(BsonDocument expectedDocument) + { + using var reader = CreateSubject(expectedDocument); + var context = BsonDeserializationContext.CreateRoot(reader); + + var actualDocument = BsonDocumentSerializer.Instance.Deserialize(context); + + Assert.True(expectedDocument.Equals(actualDocument)); + } + + private ReadOnlyMemoryBsonReader CreateSubject(BsonDocument bsonDocument) + { + var bson = bsonDocument.ToBson(); + return new ReadOnlyMemoryBsonReader(bson); + } + + private class CWithRawBsonArray : IDisposable + { + public int Id { get; set; } + public RawBsonArray A { get; set; } + + public void Dispose() + { + if (A != null) + { + A.Dispose(); + A = null; + } + } + } + + private class CWithRawBsonDocument : IDisposable + { + public int Id { get; set; } + public RawBsonDocument A { get; set; } + + public void Dispose() + { + if (A != null) + { + A.Dispose(); + A = null; + } + } + } +} From b9af7bcfdfe87f62ae3bef40a1eec7f7a94dd702 Mon Sep 17 00:00:00 2001 From: BorisDog Date: Fri, 21 Nov 2025 15:43:35 -0800 Subject: [PATCH 2/2] - PR comments --- src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs | 4 ++-- src/MongoDB.Bson/IO/TrieNameDecoder.cs | 11 ++++------- src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs | 1 - 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs b/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs index 0f53ff16cbd..34e9e66cd01 100644 --- a/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs +++ b/src/MongoDB.Bson/IO/ReadOnlyMemoryBsonReader.cs @@ -447,7 +447,7 @@ public override ObjectId ReadObjectId() } /// - /// Reads a raw BSON array. WARNING TODO + /// Reads a raw BSON array. /// /// /// The raw BSON array. @@ -656,7 +656,7 @@ private void PopContext() throw new FormatException($"Expected size to be {_context.Size}, not {actualSize}."); } - _context =_contextStack.Pop(); + _context = _contextStack.Pop(); } private void PushContext(BsonBinaryReaderContext newContext) diff --git a/src/MongoDB.Bson/IO/TrieNameDecoder.cs b/src/MongoDB.Bson/IO/TrieNameDecoder.cs index ab5f0fbd060..05fb3987331 100644 --- a/src/MongoDB.Bson/IO/TrieNameDecoder.cs +++ b/src/MongoDB.Bson/IO/TrieNameDecoder.cs @@ -104,14 +104,11 @@ public string Decode(BsonStream stream, UTF8Encoding encoding) public string Decode(ReadOnlySpan span, UTF8Encoding encoding) { BsonTrieNode node; - if (_trie.TryGetNode(span, out node)) + if (_trie.TryGetNode(span, out node) && node.HasValue) { - if (node.HasValue) - { - _found = true; - _value = node.Value; - return node.ElementName; - } + _found = true; + _value = node.Value; + return node.ElementName; } _found = false; diff --git a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs index 979c4afc8e2..b7ac7699b7a 100644 --- a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs @@ -69,7 +69,6 @@ public override int ElementCount { ThrowIfDisposed(); using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(_slice, _readerSettings); - var context = BsonDeserializationContext.CreateRoot(bsonReader); var elementCount = 0;