Skip to content

CSHARP-3399: Mitigate pain of using field names with dots and dollars. #568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/MongoDB.Bson/IO/NoOpElementNameValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
*/


using System;

namespace MongoDB.Bson.IO
{
/// <summary>
Expand Down Expand Up @@ -54,6 +56,10 @@ public IElementNameValidator GetValidatorForChildContent(string elementName)
/// <returns>True if the element name is valid.</returns>
public bool IsValidElementName(string elementName)
{
if (elementName == null)
{
throw new ArgumentNullException(nameof(elementName), "Value cannot be null.");
}
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static IElementNameValidator ForUpdateType(UpdateType updateType)
switch (updateType)
{
case UpdateType.Replacement:
return CollectionElementNameValidator.Instance;
return ReplacementElementNameValidator.Instance;
case UpdateType.Update:
return UpdateElementNameValidator.Instance;
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright 2013-present MongoDB Inc.
/* Copyright 2021-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.
Expand All @@ -13,29 +13,27 @@
* limitations under the License.
*/

using System.Collections.Generic;
using MongoDB.Bson.IO;
using MongoDB.Driver.Core.Misc;

namespace MongoDB.Driver.Core.Operations.ElementNameValidators
{
/// <summary>
/// Represents an element name validator that checks that element names are valid for MongoDB collections.
/// Represents an element name validator for replace operations.
/// </summary>
public class CollectionElementNameValidator : IElementNameValidator
public class ReplacementElementNameValidator : IElementNameValidator
{
// private static fields
private static readonly List<string> __exceptions = new List<string> { "$db", "$ref", "$id", "$code", "$scope" };
private static readonly CollectionElementNameValidator __instance = new CollectionElementNameValidator();
private static readonly ReplacementElementNameValidator __instance = new ReplacementElementNameValidator();

// public static fields
/// <summary>
/// Gets a pre-created instance of a CollectionElementNameValidator.
/// Gets a pre-created instance of an ReplacementElementNameValidator.
/// </summary>
/// <value>
/// The pre-created instance.
/// </value>
public static CollectionElementNameValidator Instance
public static ReplacementElementNameValidator Instance
{
get { return __instance; }
}
Expand All @@ -44,31 +42,14 @@ public static CollectionElementNameValidator Instance
/// <inheritdoc/>
public IElementNameValidator GetValidatorForChildContent(string elementName)
{
return this;
return NoOpElementNameValidator.Instance;
}

/// <inheritdoc/>
public bool IsValidElementName(string elementName)
{
Ensure.IsNotNull(elementName, nameof(elementName));

if (elementName.Length == 0)
{
// the server seems to allow empty element names, but in 1.x we did not
return false;
}

if (elementName.IndexOf('.') != -1)
{
return false;
}

if (elementName[0] == '$' && !__exceptions.Contains(elementName))
{
return false;
}

return true;
return elementName.Length > 0 && elementName[0] != '$';
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

using MongoDB.Bson.IO;
using MongoDB.Driver.Core.Misc;

namespace MongoDB.Driver.Core.Operations.ElementNameValidators
{
Expand Down Expand Up @@ -47,6 +48,7 @@ public IElementNameValidator GetValidatorForChildContent(string elementName)
/// <inheritdoc/>
public bool IsValidElementName(string elementName)
{
Ensure.IsNotNull(elementName, nameof(elementName));
return elementName.Length > 0 && elementName[0] == '$';
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,17 @@ public IElementNameValidator GetValidatorForChildContent(string elementName)
/// <inheritdoc/>
public bool IsValidElementName(string elementName)
{
Ensure.IsNotNull(elementName, nameof(elementName));
// the first elementName we see determines whether we are validating an update or a replacement document
if (_chosenValidator == null)
{
if (elementName.Length > 0 && elementName[0] == '$')
{
_chosenValidator = UpdateElementNameValidator.Instance; ;
_chosenValidator = UpdateElementNameValidator.Instance;
}
else
{
_chosenValidator = CollectionElementNameValidator.Instance;
_chosenValidator = ReplacementElementNameValidator.Instance;
}
}
return _chosenValidator.IsValidElementName(elementName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,27 +202,7 @@ internal override BsonDocument CreateCommand(ICoreSessionHandle session, Connect
/// <inheritdoc/>
protected override IElementNameValidator GetCommandValidator()
{
return Validator.Instance;
}

private class Validator : IElementNameValidator
{
public readonly static Validator Instance = new Validator();

public IElementNameValidator GetValidatorForChildContent(string elementName)
{
if (elementName == "update")
{
return CollectionElementNameValidator.Instance;
}

return NoOpElementNameValidator.Instance;
}

public bool IsValidElementName(string elementName)
{
return true;
}
return NoOpElementNameValidator.Instance;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ public IElementNameValidator GetValidatorForChildContent(string elementName)

public bool IsValidElementName(string elementName)
{
Ensure.IsNotNull(elementName, nameof(elementName));
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
using MongoDB.Driver.Core.Bindings;
using MongoDB.Driver.Core.Connections;
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Core.Operations.ElementNameValidators;
using MongoDB.Driver.Core.WireProtocol.Messages;
using MongoDB.Driver.Core.WireProtocol.Messages.Encoders;

Expand Down Expand Up @@ -133,8 +132,8 @@ protected override IEnumerable<Type1CommandMessageSection> CreateCommandPayloads
{
documents = new BatchableSource<TDocument>(_documents.Items, _documents.Offset, _documents.ProcessedCount, canBeSplit: false);
}
var isSystemIndexesCollection = _collectionNamespace.Equals(CollectionNamespace.DatabaseNamespace.SystemIndexesCollection);
var elementNameValidator = isSystemIndexesCollection ? (IElementNameValidator)NoOpElementNameValidator.Instance : CollectionElementNameValidator.Instance;

var elementNameValidator = NoOpElementNameValidator.Instance;
var maxBatchCount = Math.Min(MaxBatchCount ?? int.MaxValue, channel.ConnectionDescription.MaxBatchCount);
var maxDocumentSize = channel.ConnectionDescription.MaxDocumentSize;
var payload = new Type1CommandMessageSection<TDocument>("documents", documents, _documentSerializer, elementNameValidator, maxBatchCount, maxDocumentSize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,49 +142,37 @@ private void WriteDocuments(BsonBinaryWriter writer, long messageStartPosition,
var stream = writer.BsonStream;
var context = BsonSerializationContext.CreateRoot(writer);

var collectionNamespace = message.CollectionNamespace;
var isSystemIndexesCollection = collectionNamespace.Equals(collectionNamespace.DatabaseNamespace.SystemIndexesCollection);
var elementNameValidator = isSystemIndexesCollection ? (IElementNameValidator)NoOpElementNameValidator.Instance : CollectionElementNameValidator.Instance;

writer.PushElementNameValidator(elementNameValidator);
try
var documentSource = message.DocumentSource;
var batchCount = Math.Min(documentSource.Count, message.MaxBatchCount);
if (batchCount < documentSource.Count && !documentSource.CanBeSplit)
{
var documentSource = message.DocumentSource;
var batchCount = Math.Min(documentSource.Count, message.MaxBatchCount);
if (batchCount < documentSource.Count && !documentSource.CanBeSplit)
{
throw new BsonSerializationException("Batch is too large.");
}
throw new BsonSerializationException("Batch is too large.");
}

for (var i = 0; i < batchCount; i++)
{
var document = documentSource.Items[documentSource.Offset + i];
var documentStartPosition = stream.Position;
for (var i = 0; i < batchCount; i++)
{
var document = documentSource.Items[documentSource.Offset + i];
var documentStartPosition = stream.Position;

_serializer.Serialize(context, document);
_serializer.Serialize(context, document);

var messageSize = stream.Position - messageStartPosition;
if (messageSize > message.MaxMessageSize)
var messageSize = stream.Position - messageStartPosition;
if (messageSize > message.MaxMessageSize)
{
if (i > 0 && documentSource.CanBeSplit)
{
stream.Position = documentStartPosition;
stream.SetLength(documentStartPosition);
documentSource.SetProcessedCount(i);
return;
}
else
{
if (i > 0 && documentSource.CanBeSplit)
{
stream.Position = documentStartPosition;
stream.SetLength(documentStartPosition);
documentSource.SetProcessedCount(i);
return;
}
else
{
throw new BsonSerializationException("Batch is too large.");
}
throw new BsonSerializationException("Batch is too large.");
}
}
documentSource.SetProcessedCount(batchCount);
}
finally
{
writer.PopElementNameValidator();
}
documentSource.SetProcessedCount(batchCount);
}

// explicit interface implementations
Expand Down
14 changes: 0 additions & 14 deletions tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp253Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,6 @@ public void TestInsertClass()
_collection.Insert(c);
}

[Fact]
public void TestInsertDollar()
{
Assert.Throws<BsonSerializationException>(() => { _collection.Insert(new BsonDocument("$x", 1)); });
Assert.Throws<BsonSerializationException>(() => { _collection.Insert(new BsonDocument("x", new BsonDocument("$x", 1))); });
}

[Fact]
public void TestInsertPeriod()
{
Assert.Throws<BsonSerializationException>(() => { _collection.Insert(new BsonDocument("a.b", 1)); });
Assert.Throws<BsonSerializationException>(() => { _collection.Insert(new BsonDocument("a", new BsonDocument("b.c", 1))); });
}

[Fact]
public void TestLegacyDollar()
{
Expand Down
52 changes: 44 additions & 8 deletions tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp265Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@ public void TestGenericDictionaryDocumentRepresentationWithDollar()
var json = d.ToJson();
Assert.Equal(expected, json);

Assert.Throws<BsonSerializationException>(() => { __collection.Insert(d); });
__collection.RemoveAll();
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["$a"]);
}

[Fact]
Expand All @@ -132,7 +137,12 @@ public void TestGenericDictionaryDocumentRepresentationWithDot()
var json = d.ToJson();
Assert.Equal(expected, json);

Assert.Throws<BsonSerializationException>(() => { __collection.Insert(d); });
__collection.RemoveAll();
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["a.b"]);
}

[Fact]
Expand Down Expand Up @@ -160,7 +170,11 @@ public void TestGenericDictionaryDynamicRepresentationWithDollar()
Assert.Equal(expected, json);

__collection.RemoveAll();
Assert.Throws<BsonSerializationException>(() => __collection.Insert(d));
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["$a"]);
}

[Fact]
Expand All @@ -172,7 +186,11 @@ public void TestGenericDictionaryDynamicRepresentationWithDot()
Assert.Equal(expected, json);

__collection.RemoveAll();
Assert.Throws<BsonSerializationException>(() => __collection.Insert(d));
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["a.b"]);
}

[Fact]
Expand Down Expand Up @@ -215,7 +233,12 @@ public void TestHashtableDocumentRepresentationWithDollar()
var json = d.ToJson();
Assert.Equal(expected, json);

Assert.Throws<BsonSerializationException>(() => { __collection.Insert(d); });
__collection.RemoveAll();
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["$a"]);
}

[Fact]
Expand All @@ -226,7 +249,12 @@ public void TestHashtableDocumentRepresentationWithDot()
var json = d.ToJson();
Assert.Equal(expected, json);

Assert.Throws<BsonSerializationException>(() => { __collection.Insert(d); });
__collection.RemoveAll();
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["a.b"]);
}

[Fact]
Expand Down Expand Up @@ -254,7 +282,11 @@ public void TestHashtableDynamicRepresentationWithDollar()
Assert.Equal(expected, json);

__collection.RemoveAll();
Assert.Throws<BsonSerializationException>(() => __collection.Insert(d));
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["$a"]);
}

[Fact]
Expand All @@ -266,7 +298,11 @@ public void TestHashtableDynamicRepresentationWithDot()
Assert.Equal(expected, json);

__collection.RemoveAll();
Assert.Throws<BsonSerializationException>(() => __collection.Insert(d));
__collection.Insert(d);
var r = __collection.FindOne(Query.EQ("_id", d.Id));
Assert.Equal(d.Id, r.Id);
Assert.Equal(1, r.Data.Count);
Assert.Equal(1, r.Data["a.b"]);
}
}
}
Loading