Skip to content

Commit 2d16c70

Browse files
committed
CSHARP-5670: Implement ReadOnlyMemoryBsonReader
1 parent 5de47d4 commit 2d16c70

36 files changed

+2499
-696
lines changed

src/MongoDB.Bson/IO/BsonBinaryReader.cs

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18-
using System.Globalization;
1918
using System.IO;
2019

2120
namespace MongoDB.Bson.IO
@@ -90,7 +89,7 @@ public BsonStream BsonStream
9089
}
9190

9291
/// <summary>
93-
/// Gets the settings of the writer.
92+
/// Gets the settings of the reader.
9493
/// </summary>
9594
public new BsonBinaryReaderSettings Settings
9695
{
@@ -214,7 +213,7 @@ public override BsonType ReadBsonType()
214213
{
215214
// insert the element name into the error message
216215
var periodIndex = ex.Message.IndexOf('.');
217-
var dottedElementName = GenerateDottedElementName();
216+
var dottedElementName = BsonBinaryReaderUtils.GenerateDottedElementName(_context, _contextStack.ToArray(), () => _bsonStream.ReadCString(Utf8Encodings.Lenient));
218217
var message = ex.Message.Substring(0, periodIndex) + $" for fieldname \"{dottedElementName}\"" + ex.Message.Substring(periodIndex);
219218
throw new FormatException(message);
220219
}
@@ -758,59 +757,6 @@ protected override void Dispose(bool disposing)
758757
}
759758

760759
// private methods
761-
private string GenerateDottedElementName()
762-
{
763-
string elementName;
764-
if (_context.ContextType == ContextType.Document)
765-
{
766-
try
767-
{
768-
elementName = _bsonStream.ReadCString(Utf8Encodings.Lenient);
769-
}
770-
catch
771-
{
772-
elementName = "?"; // ignore exception
773-
}
774-
}
775-
else if (_context.ContextType == ContextType.Array)
776-
{
777-
elementName = _context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
778-
}
779-
else
780-
{
781-
elementName = "?";
782-
}
783-
784-
return GenerateDottedElementName(_contextStack.ToArray(), 0, elementName);
785-
}
786-
787-
private string GenerateDottedElementName(BsonBinaryReaderContext[] contexts, int currentContextIndex, string elementName)
788-
{
789-
if (currentContextIndex >= contexts.Length)
790-
return elementName;
791-
792-
var context = contexts[currentContextIndex];
793-
var nextIndex = currentContextIndex + 1;
794-
795-
if (context.ContextType == ContextType.Document)
796-
{
797-
return GenerateDottedElementName(contexts, nextIndex, (context.ElementName ?? "?") + "." + elementName);
798-
}
799-
800-
if (context.ContextType == ContextType.Array)
801-
{
802-
var indexElementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
803-
return GenerateDottedElementName(contexts, nextIndex, indexElementName + "." + elementName);
804-
}
805-
806-
if (nextIndex < contexts.Length)
807-
{
808-
return GenerateDottedElementName(contexts, nextIndex, "?." + elementName);
809-
}
810-
811-
return elementName;
812-
}
813-
814760
private BsonReaderState GetNextState()
815761
{
816762
switch (_context.ContextType)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Globalization;
18+
19+
namespace MongoDB.Bson.IO;
20+
21+
/// <summary>
22+
/// Provides utility methods for working with <see cref="IBsonReader"/> instances in binary BSON format.
23+
/// </summary>
24+
public static class BsonBinaryReaderUtils
25+
{
26+
/// <summary>
27+
/// Creates an instance of <see cref="IBsonReader"/> for the given byte buffer and reader settings.
28+
/// The result <see cref="IBsonReader"/> instance does not own the byte buffer, and will not Dispose it.
29+
/// For continuous single chunk buffers an optimized implementation of <see cref="IBsonReader"/> is created.
30+
/// </summary>
31+
/// <param name="byteBuffer">The byte buffer containing BSON data.</param>
32+
/// <param name="settings">The settings to configure the BSON reader.</param>
33+
/// <returns>An <see cref="IBsonReader"/>Bson reader.</returns>
34+
public static IBsonReader CreateBinaryReader(IByteBuffer byteBuffer, BsonBinaryReaderSettings settings)
35+
{
36+
if (byteBuffer is ReadOnlyMemoryBuffer readOnlyMemoryBuffer)
37+
{
38+
return new ReadOnlyMemoryBsonReader(readOnlyMemoryBuffer.Memory, new ReadOnlyMemoryReaderSettings(settings));
39+
}
40+
41+
var backingBytes = byteBuffer.AccessBackingBytes(0);
42+
if (backingBytes.Count == byteBuffer.Length)
43+
{
44+
return new ReadOnlyMemoryBsonReader(backingBytes, new ByteBufferSlicer(byteBuffer), new ReadOnlyMemoryReaderSettings(settings));
45+
}
46+
47+
var stream = new ByteBufferStream(byteBuffer, ownsBuffer: false);
48+
return new BsonBinaryReader(stream, settings);
49+
}
50+
51+
internal static string GenerateDottedElementName(BsonBinaryReaderContext context, BsonBinaryReaderContext[] parentContexts, Func<string> elementNameReader)
52+
{
53+
string elementName;
54+
if (context.ContextType == ContextType.Document)
55+
{
56+
try
57+
{
58+
elementName = elementNameReader();
59+
}
60+
catch
61+
{
62+
elementName = "?"; // ignore exception
63+
}
64+
}
65+
else if (context.ContextType == ContextType.Array)
66+
{
67+
elementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
68+
}
69+
else
70+
{
71+
elementName = "?";
72+
}
73+
74+
return GenerateDottedElementName(parentContexts, 0, elementName);
75+
}
76+
77+
private static string GenerateDottedElementName(BsonBinaryReaderContext[] contexts, int currentContextIndex, string elementName)
78+
{
79+
if (currentContextIndex >= contexts.Length)
80+
return elementName;
81+
82+
var context = contexts[currentContextIndex];
83+
var nextIndex = currentContextIndex + 1;
84+
85+
if (context.ContextType == ContextType.Document)
86+
{
87+
return GenerateDottedElementName(contexts, nextIndex, (context.ElementName ?? "?") + "." + elementName);
88+
}
89+
90+
if (context.ContextType == ContextType.Array)
91+
{
92+
var indexElementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
93+
return GenerateDottedElementName(contexts, nextIndex, indexElementName + "." + elementName);
94+
}
95+
96+
if (nextIndex < contexts.Length)
97+
{
98+
return GenerateDottedElementName(contexts, nextIndex, "?." + elementName);
99+
}
100+
101+
return elementName;
102+
}
103+
}

src/MongoDB.Bson/IO/BsonReader.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -466,9 +466,17 @@ protected void ThrowObjectDisposedException()
466466
/// </summary>
467467
/// <param name="methodName">The name of the method calling this one.</param>
468468
/// <param name="requiredBsonType">The required BSON type.</param>
469-
protected void VerifyBsonType(string methodName, BsonType requiredBsonType)
469+
protected void VerifyBsonType(string methodName, BsonType requiredBsonType) =>
470+
VerifyBsonType(requiredBsonType, methodName);
471+
472+
/// <summary>
473+
/// Verifies the current state and BsonType of the reader.
474+
/// </summary>
475+
/// /// <param name="requiredBsonType">The required BSON type.</param>
476+
/// <param name="methodName">The name of the method calling this one.</param>
477+
protected void VerifyBsonType(BsonType requiredBsonType, [System.Runtime.CompilerServices.CallerMemberName]string methodName = null)
470478
{
471-
if (_state == BsonReaderState.Initial || _state == BsonReaderState.ScopeDocument || _state == BsonReaderState.Type)
479+
if (_state is BsonReaderState.Initial or BsonReaderState.ScopeDocument or BsonReaderState.Type)
472480
{
473481
ReadBsonType();
474482
}
@@ -483,10 +491,7 @@ protected void VerifyBsonType(string methodName, BsonType requiredBsonType)
483491
}
484492
if (_currentBsonType != requiredBsonType)
485493
{
486-
var message = string.Format(
487-
"{0} can only be called when CurrentBsonType is {1}, not when CurrentBsonType is {2}.",
488-
methodName, requiredBsonType, _currentBsonType);
489-
throw new InvalidOperationException(message);
494+
throw new InvalidOperationException($"{methodName} can only be called when CurrentBsonType is {requiredBsonType}, not when CurrentBsonType is {_currentBsonType}.");
490495
}
491496
}
492497
}

src/MongoDB.Bson/IO/BsonStream.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,13 @@
1414
*/
1515

1616
using System;
17-
using System.Collections.Generic;
1817
using System.IO;
19-
using System.Linq;
2018
using System.Text;
21-
using System.Threading.Tasks;
2219

2320
namespace MongoDB.Bson.IO
2421
{
2522
/// <summary>
26-
/// Represents a Stream has additional methods to suport reading and writing BSON values.
23+
/// Represents a Stream has additional methods to support reading and writing BSON values.
2724
/// </summary>
2825
public abstract class BsonStream : Stream
2926
{

src/MongoDB.Bson/IO/BsonStreamExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ public static void BackpatchSize(this BsonStream stream, long startPosition)
6565
stream.Position = endPosition;
6666
}
6767

68+
/// <summary>
69+
/// Determines whether the specified BSON type is valid.
70+
/// </summary>
71+
/// <param name="bsonType">The BSON type to validate.</param>
72+
/// <returns>True if the BSON type is valid; otherwise, false.</returns>
73+
public static bool IsValidBsonType(BsonType bsonType) => __validBsonTypes[(byte)bsonType];
74+
6875
/// <summary>
6976
/// Reads the binary sub type.
7077
/// </summary>

src/MongoDB.Bson/IO/BsonTrie.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ public bool TryGetNode(ArraySegment<byte> utf8, out BsonTrieNode<TValue> node)
9393
return node != null;
9494
}
9595

96+
/// <summary>
97+
/// Gets the node associated with the specified element name.
98+
/// </summary>
99+
/// <param name="utf8">The element name.</param>
100+
/// <param name="node">
101+
/// When this method returns, contains the node associated with the specified element name, if the key is found;
102+
/// otherwise, null. This parameter is passed uninitialized.
103+
/// </param>
104+
/// <returns>True if the node was found; otherwise, false.</returns>
105+
public bool TryGetNode(ReadOnlySpan<byte> utf8, out BsonTrieNode<TValue> node)
106+
{
107+
node = _root;
108+
for (var i = 0; node != null && i < utf8.Length; i++)
109+
{
110+
var keyByte = utf8[i];
111+
node = node.GetChild(keyByte);
112+
}
113+
114+
return node != null;
115+
}
116+
96117
/// <summary>
97118
/// Tries to get the node associated with a name read from a stream.
98119
/// </summary>

src/MongoDB.Bson/IO/BsonWriter.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -347,15 +347,13 @@ public virtual void WriteRawBsonDocument(IByteBuffer slice)
347347
// overridden in BsonBinaryWriter to write the raw bytes to the stream
348348
// for all other streams, deserialize the raw bytes and serialize the resulting document instead
349349

350-
using (var stream = new ByteBufferStream(slice, ownsBuffer: false))
351-
using (var bsonReader = new BsonBinaryReader(stream, BsonBinaryReaderSettings.Defaults))
352-
{
353-
var deserializationContext = BsonDeserializationContext.CreateRoot(bsonReader);
354-
var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext);
350+
using var bsonReader = BsonBinaryReaderUtils.CreateBinaryReader(slice, BsonBinaryReaderSettings.Defaults);
355351

356-
var serializationContext = BsonSerializationContext.CreateRoot(this);
357-
BsonDocumentSerializer.Instance.Serialize(serializationContext, document);
358-
}
352+
var deserializationContext = BsonDeserializationContext.CreateRoot(bsonReader);
353+
var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext);
354+
355+
var serializationContext = BsonSerializationContext.CreateRoot(this);
356+
BsonDocumentSerializer.Instance.Serialize(serializationContext, document);
359357
}
360358

361359
/// <summary>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
18+
namespace MongoDB.Bson.IO;
19+
20+
internal interface IByteBufferSlicer
21+
{
22+
public IByteBuffer GetSlice(int position, int length);
23+
}
24+
25+
internal sealed class ReadOnlyMemorySlicer(ReadOnlyMemory<byte> ReadOnlyMemory) : IByteBufferSlicer
26+
{
27+
public IByteBuffer GetSlice(int position, int length)
28+
{
29+
var slice = ReadOnlyMemory.Slice(position, length);
30+
return new ReadOnlyMemoryBuffer(slice, new ReadOnlyMemorySlicer(slice));
31+
}
32+
}
33+
34+
internal sealed class ByteBufferSlicer(IByteBuffer ByteBuffer) : IByteBufferSlicer
35+
{
36+
public IByteBuffer GetSlice(int position, int length) => ByteBuffer.GetSlice(position, length);
37+
}

src/MongoDB.Bson/IO/INameDecoder.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
*/
1515

1616
using System;
17-
using System.IO;
1817
using System.Text;
1918

2019
namespace MongoDB.Bson.IO
@@ -40,4 +39,9 @@ public interface INameDecoder
4039
/// <param name="name">The name.</param>
4140
void Inform(string name);
4241
}
42+
43+
internal interface INameDecoderInternal
44+
{
45+
string Decode(ReadOnlySpan<byte> span, UTF8Encoding encoding);
46+
}
4347
}

0 commit comments

Comments
 (0)