From 5b7e6b36ae6b48a6c27d14ec7f0555f123253d55 Mon Sep 17 00:00:00 2001 From: Adam Milazzo Date: Thu, 9 Aug 2018 13:21:44 -0700 Subject: [PATCH 1/2] MongoDB.Bson/ObjectModel/BsonDocument.cs: * Made DeepClone work with documents that have duplicate element names * Added constructors that take documents and work if they have duplicate names MongoDB.Bson/ObjectModel/LazyBsonDocument.cs: MongoDB.Bson/ObjectModel/RawBsonDocument.cs: * Set AllowDuplicateNames to true since raw BSON always might have duplicate names --- src/MongoDB.Bson/ObjectModel/BsonDocument.cs | 33 ++++++++++++++++++- .../ObjectModel/LazyBsonDocument.cs | 1 + .../ObjectModel/RawBsonDocument.cs | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/MongoDB.Bson/ObjectModel/BsonDocument.cs b/src/MongoDB.Bson/ObjectModel/BsonDocument.cs index 0b57ad3aa01..02c650cbee9 100644 --- a/src/MongoDB.Bson/ObjectModel/BsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/BsonDocument.cs @@ -68,6 +68,37 @@ public BsonDocument(BsonElement element) Add(element); } + /// + /// Initializes a new instance of the BsonDocument by coping elements from another BsonDocument. + /// + /// The document whose elements will be copied + public BsonDocument(BsonDocument document) + { + if (document == null) + { + throw new ArgumentNullException("document"); + } + + _allowDuplicateNames = document.AllowDuplicateNames; + AddRange(document); + } + + /// + /// Initializes a new instance of the BsonDocument by coping elements from a LazyBsonDocument. + /// + /// The document whose elements will be copied + public BsonDocument(LazyBsonDocument document) : this((BsonDocument)document) + { + } + + /// + /// Initializes a new instance of the BsonDocument by coping elements from a RawBsonDocument. + /// + /// The document whose elements will be copied + public BsonDocument(RawBsonDocument document) : this((BsonDocument)document) + { + } + /// /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs. /// @@ -818,7 +849,7 @@ public virtual bool ContainsValue(BsonValue value) /// A deep clone of the document. public override BsonValue DeepClone() { - BsonDocument clone = new BsonDocument(); + BsonDocument clone = new BsonDocument() { AllowDuplicateNames = AllowDuplicateNames }; foreach (BsonElement element in _elements) { clone.Add(element.DeepClone()); diff --git a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs index a95e766a3b8..f1a1b70510a 100644 --- a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs @@ -50,6 +50,7 @@ public LazyBsonDocument(IByteBuffer slice) throw new ArgumentNullException("slice"); } + AllowDuplicateNames = true; // raw BSON always supports duplicate names _slice = slice; } diff --git a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs index cffaa180694..48133a40789 100644 --- a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs @@ -51,6 +51,7 @@ public RawBsonDocument(IByteBuffer slice) throw new ArgumentNullException("slice"); } + AllowDuplicateNames = true; // raw BSON always supports duplicate names _slice = slice; } From 8cdbcc01fa31467777c8ecf2c0c993d3eb3ed50a Mon Sep 17 00:00:00 2001 From: BorisDog Date: Tue, 5 Mar 2024 14:57:56 -0800 Subject: [PATCH 2/2] CSHARP-2353: BsonDocument.DeepClone() fails on documents with duplicate element names --- src/MongoDB.Bson/ObjectModel/BsonDocument.cs | 47 +++++---------- .../ObjectModel/LazyBsonDocument.cs | 1 - .../ObjectModel/RawBsonDocument.cs | 1 - .../ObjectModel/BsonDocumentTests.cs | 60 +++++++++++++++++++ 4 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/MongoDB.Bson/ObjectModel/BsonDocument.cs b/src/MongoDB.Bson/ObjectModel/BsonDocument.cs index 02c650cbee9..ae45c85f6a1 100644 --- a/src/MongoDB.Bson/ObjectModel/BsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/BsonDocument.cs @@ -68,36 +68,21 @@ public BsonDocument(BsonElement element) Add(element); } - /// - /// Initializes a new instance of the BsonDocument by coping elements from another BsonDocument. - /// - /// The document whose elements will be copied - public BsonDocument(BsonDocument document) - { - if (document == null) - { - throw new ArgumentNullException("document"); - } - - _allowDuplicateNames = document.AllowDuplicateNames; - AddRange(document); - } - - /// - /// Initializes a new instance of the BsonDocument by coping elements from a LazyBsonDocument. - /// + /// + /// Initializes a new instance of the BsonDocument by coping elements from another BsonDocument. + /// /// The document whose elements will be copied - public BsonDocument(LazyBsonDocument document) : this((BsonDocument)document) - { - } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public BsonDocument(BsonDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } - /// - /// Initializes a new instance of the BsonDocument by coping elements from a RawBsonDocument. - /// - /// The document whose elements will be copied - public BsonDocument(RawBsonDocument document) : this((BsonDocument)document) - { - } + _allowDuplicateNames = document.AllowDuplicateNames; + AddRange(document); + } /// /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs. @@ -764,7 +749,7 @@ public virtual void Clear() /// A shallow clone of the document. public override BsonValue Clone() { - BsonDocument clone = new BsonDocument(); + BsonDocument clone = new BsonDocument() { AllowDuplicateNames = AllowDuplicateNames }; foreach (BsonElement element in _elements) { clone.Add(element.Clone()); @@ -776,7 +761,7 @@ public override BsonValue Clone() /// Compares this document to another document. /// /// The other document. - /// A 32-bit signed integer that indicates whether this document is less than, equal to, or greather than the other. + /// A 32-bit signed integer that indicates whether this document is less than, equal to, or greater than the other. public virtual int CompareTo(BsonDocument rhs) { if (rhs == null) { return 1; } @@ -807,7 +792,7 @@ public virtual int CompareTo(BsonDocument rhs) /// Compares the BsonDocument to another BsonValue. /// /// The other BsonValue. - /// A 32-bit signed integer that indicates whether this BsonDocument is less than, equal to, or greather than the other BsonValue. + /// A 32-bit signed integer that indicates whether this BsonDocument is less than, equal to, or greater than the other BsonValue. public override int CompareTo(BsonValue other) { if (other == null) { return 1; } diff --git a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs index f1a1b70510a..a95e766a3b8 100644 --- a/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/LazyBsonDocument.cs @@ -50,7 +50,6 @@ public LazyBsonDocument(IByteBuffer slice) throw new ArgumentNullException("slice"); } - AllowDuplicateNames = true; // raw BSON always supports duplicate names _slice = slice; } diff --git a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs index 48133a40789..cffaa180694 100644 --- a/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs +++ b/src/MongoDB.Bson/ObjectModel/RawBsonDocument.cs @@ -51,7 +51,6 @@ public RawBsonDocument(IByteBuffer slice) throw new ArgumentNullException("slice"); } - AllowDuplicateNames = true; // raw BSON always supports duplicate names _slice = slice; } diff --git a/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentTests.cs b/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentTests.cs index 9489c93a7e6..bf73ded3e68 100644 --- a/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentTests.cs +++ b/tests/MongoDB.Bson.Tests/ObjectModel/BsonDocumentTests.cs @@ -137,6 +137,21 @@ public void TestClone() var clone = (BsonDocument)document.Clone(); Assert.Equal(clone, document); Assert.Same(clone["d"], document["d"]); + Assert.False(clone.AllowDuplicateNames); + } + + [Fact] + public void TestClone_with_duplicate_elements() + { + var document = new BsonDocument(allowDuplicateNames: true) + { + { "d", new BsonDocument("x", 1) }, + { "d", new BsonDocument("x", 2) }, + }; + + var clone = (BsonDocument)document.Clone(); + clone.Should().Be(document); + clone.AllowDuplicateNames.Should().BeTrue(); } [Fact] @@ -153,6 +168,7 @@ public void TestConstructorElement() { var element = new BsonElement("x", 1); var document = new BsonDocument(element); + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -168,6 +184,7 @@ public void TestConstructorElements() new BsonElement("y", 2) }; var document = new BsonDocument((IEnumerable)elements); + Assert.False(document.AllowDuplicateNames); Assert.Equal(2, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(2, document["y"].AsInt32); @@ -184,6 +201,7 @@ public void TestConstructorElementsDocument() { var originalDocument = new BsonDocument { { "x", 1 }, { "y", 2 } }; var document = new BsonDocument(originalDocument); + Assert.False(document.AllowDuplicateNames); Assert.Equal(2, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(2, document["y"].AsInt32); @@ -195,6 +213,20 @@ public void TestConstructorElementsDocument() Assert.Same(originalDocument.GetElement("y").Value, document.GetElement("y").Value); } + [Fact] + public void TestConstructorElementsDocumentDuplicateNames() + { + var documentA = new BsonDocument(allowDuplicateNames: true) + { + { "x", 1 }, + { "x", 2 }, + }; + var documentB = new BsonDocument(documentA); + + documentB.AllowDuplicateNames.Should().BeTrue(); + documentB.Elements.ShouldAllBeEquivalentTo(documentA.Elements); + } + [Fact] public void TestConstructorElementsParams() { @@ -203,6 +235,8 @@ public void TestConstructorElementsParams() #pragma warning disable 618 var document = new BsonDocument(element1, element2); #pragma warning restore + Assert.False(document.AllowDuplicateNames); + Assert.Equal(2, document.ElementCount); Assert.Equal(2, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(2, document["y"].AsInt32); @@ -219,6 +253,7 @@ public void TestConstructorDictionaryGeneric() { var dictionary = new Dictionary { { "x", 1 } }; var document = new BsonDocument(dictionary); + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -233,6 +268,7 @@ public void TestConstructorDictionaryGenericWithKeys() #pragma warning disable 618 var document = new BsonDocument(dictionary, keys); #pragma warning restore + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -244,6 +280,7 @@ public void TestConstructorIDictionary() { var hashtable = (IDictionary)new Hashtable { { "x", 1 } }; var document = new BsonDocument(hashtable); + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -255,6 +292,7 @@ public void TestConstructorIDictionaryGeneric() { var dictionary = (IDictionary)new Dictionary { { "x", 1 } }; var document = new BsonDocument(dictionary); + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -269,6 +307,7 @@ public void TestConstructorIDictionaryGenericWithKeys() #pragma warning disable 618 var document = new BsonDocument(dictionary, keys); #pragma warning restore + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -283,6 +322,7 @@ public void TestConstructorIDictionaryWithKeys() #pragma warning disable 618 var document = new BsonDocument(hashtable, keys); #pragma warning restore + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -293,6 +333,7 @@ public void TestConstructorIDictionaryWithKeys() public void TestConstructorNameValue() { var document = new BsonDocument("x", 1); + Assert.False(document.AllowDuplicateNames); Assert.Equal(1, document.ElementCount); Assert.Equal(1, document["x"].AsInt32); Assert.Equal(true, document.Contains("x")); @@ -374,6 +415,25 @@ public void TestDeepClone() var clone = (BsonDocument)document.DeepClone(); Assert.Equal(clone, document); Assert.NotSame(clone["d"], document["d"]); + Assert.False(((BsonDocument)document["d"]).AllowDuplicateNames); + } + + [Fact] + public void TestDeepClone_with_duplicate_elements() + { + var documentWithDuplicateElements = new BsonDocument(allowDuplicateNames: true) + { + { "x", 1 }, + { "x", 2 } + }; + + var document = new BsonDocument("d", documentWithDuplicateElements); + var clone = (BsonDocument)document.DeepClone(); + var clonedNestedDocument = (BsonDocument)clone["d"]; + + clonedNestedDocument.Should().NotBeSameAs((BsonDocument)document["d"]); + clonedNestedDocument.Elements.ShouldAllBeEquivalentTo(documentWithDuplicateElements.Elements); + clonedNestedDocument.AllowDuplicateNames.Should().BeTrue(); } [Fact]