Skip to content

Commit 786e324

Browse files
CSHARP-4293: Update with aggregation pipeline fails when used on a OfTypeMongoCollection (OfType<T>). (#869)
1 parent 7ddee4b commit 786e324

File tree

5 files changed

+135
-9
lines changed

5 files changed

+135
-9
lines changed

src/MongoDB.Driver.Core/Core/Misc/Feature.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public class Feature
116116
private static readonly Feature __streamingHello = new Feature("StreamingHello", WireVersion.Server44);
117117
private static readonly Feature __tailableCursor = new Feature("TailableCursor", WireVersion.Server32);
118118
private static readonly Feature __transactions = new Feature("Transactions", WireVersion.Server40);
119+
private static readonly Feature __updateWithAggregationPipeline = new Feature("UpdateWithAggregationPipeline", WireVersion.Server42);
119120
private static readonly Feature __userManagementCommands = new Feature("UserManagementCommands", WireVersion.Server26);
120121
private static readonly Feature __views = new Feature("Views", WireVersion.Server34);
121122
private static readonly Feature __wildcardIndexes = new Feature("WildcardIndexes", WireVersion.Server42);
@@ -651,6 +652,11 @@ public class Feature
651652
[Obsolete("This property will be removed in a later release.")]
652653
public static Feature UserManagementCommands => __userManagementCommands;
653654

655+
/// <summary>
656+
/// Gets the update with aggregation pipeline feature.
657+
/// </summary>
658+
public static Feature UpdateWithAggregationPipeline => __updateWithAggregationPipeline;
659+
654660
/// <summary>
655661
/// Gets the views feature.
656662
/// </summary>

src/MongoDB.Driver/FilteredMongoCollectionBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ private IEnumerable<WriteModel<TDocument>> CombineModelFilters(IEnumerable<Write
404404
default:
405405
throw new MongoInternalException("Request type is invalid.");
406406
}
407-
});
407+
}).ToList();
408408
}
409409

410410
private PipelineDefinition<TDocument, TResult> CreateFilteredPipeline<TResult>(PipelineDefinition<TDocument, TResult> pipeline)

src/MongoDB.Driver/OfTypeMongoCollection.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using MongoDB.Bson;
1617
using MongoDB.Bson.Serialization;
1718

1819
namespace MongoDB.Driver
@@ -61,11 +62,43 @@ protected override UpdateDefinition<TDerivedDocument> AdjustUpdateDefinition(Upd
6162
if (isUpsert)
6263
{
6364
var discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(typeof(TDerivedDocument));
65+
var discriminatorConventionElementName = discriminatorConvention.ElementName;
6466
var discriminatorValue = discriminatorConvention.GetDiscriminator(typeof(TRootDocument), typeof(TDerivedDocument));
6567

66-
var builder = new UpdateDefinitionBuilder<TDerivedDocument>();
67-
var setOnInsertDiscriminator = builder.SetOnInsert(discriminatorConvention.ElementName, discriminatorValue);
68-
result = builder.Combine(result, setOnInsertDiscriminator);
68+
if (result is PipelineUpdateDefinition<TDerivedDocument> pipeline)
69+
{
70+
var setOnInsertStage = new BsonDocument()
71+
{
72+
{
73+
"$set",
74+
new BsonDocument
75+
{
76+
{
77+
discriminatorConventionElementName, // target field
78+
new BsonDocument // condition
79+
{
80+
{
81+
"$cond",
82+
new BsonArray
83+
{
84+
new BsonDocument("$eq", new BsonArray { new BsonDocument("$type", "$_id"), "missing" }), // if "_id" is missed
85+
discriminatorValue, // then set targetField to discriminatorValue
86+
$"${discriminatorConventionElementName}" // else set targetField from the value in the document
87+
}
88+
}
89+
}
90+
}
91+
}
92+
}
93+
};
94+
result = pipeline.Pipeline.AppendStage<TDerivedDocument, TDerivedDocument, TDerivedDocument>(setOnInsertStage);
95+
}
96+
else
97+
{
98+
var builder = new UpdateDefinitionBuilder<TDerivedDocument>();
99+
var setOnInsertDiscriminator = builder.SetOnInsert(discriminatorConventionElementName, discriminatorValue);
100+
result = builder.Combine(result, setOnInsertDiscriminator);
101+
}
69102
}
70103

71104
return result;

src/MongoDB.Driver/UpdateDefinition.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ public PipelineUpdateDefinition(PipelineDefinition<TDocument, TDocument> pipelin
115115
_pipeline = Ensure.IsNotNull(pipeline, nameof(pipeline));
116116
}
117117

118+
/// <summary>
119+
/// Gets the pipeline.
120+
/// </summary>
121+
public PipelineDefinition<TDocument, TDocument> Pipeline => _pipeline;
122+
118123
/// <summary>
119124
/// Renders the update to a <see cref="BsonValue"/> that represents <see cref="BsonArray"/>.
120125
/// </summary>

tests/MongoDB.Driver.Tests/OfTypeMongoCollectionTests.cs

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@
2323
using MongoDB.Bson.Serialization;
2424
using MongoDB.Bson.Serialization.Attributes;
2525
using MongoDB.Bson.TestHelpers.XunitExtensions;
26+
using MongoDB.Driver.Core;
2627
using MongoDB.Driver.Core.Bindings;
28+
using MongoDB.Driver.Core.Events;
29+
using MongoDB.Driver.Core.Misc;
30+
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
31+
using MongoDB.Driver.TestHelpers;
2732
using Moq;
2833
using Xunit;
2934

@@ -947,15 +952,21 @@ public class UpdateTestCases : IValueGenerator
947952
}
948953
}
949954

950-
public class OfTypeCollectionIntegrationTests
955+
public class OfTypeCollectionIntegrationTests : IDisposable
951956
{
952-
private IMongoCollection<BsonDocument> _docsCollection;
953-
private IMongoCollection<A> _rootCollection;
957+
private readonly IMongoCollection<BsonDocument> _docsCollection;
958+
private readonly EventCapturer _eventsCapturer;
959+
private readonly IMongoCollection<A> _rootCollection;
960+
private readonly DisposableMongoClient _client;
954961

955962
public OfTypeCollectionIntegrationTests()
956963
{
957-
var client = DriverTestConfiguration.Client;
958-
var db = client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName);
964+
var clientSettings = DriverTestConfiguration.Client.Settings.Clone();
965+
_eventsCapturer = new EventCapturer().Capture<CommandStartedEvent>();
966+
clientSettings.ClusterConfigurator = (b) => b.Subscribe(_eventsCapturer);
967+
_client = DriverTestConfiguration.CreateDisposableClient(clientSettings);
968+
969+
var db = _client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName);
959970
db.DropCollection(DriverTestConfiguration.CollectionNamespace.CollectionName);
960971

961972
_docsCollection = db.GetCollection<BsonDocument>(DriverTestConfiguration.CollectionNamespace.CollectionName);
@@ -1119,11 +1130,82 @@ public void ReplaceOne_should_match_document_of_right_type(
11191130
});
11201131
}
11211132

1133+
[SkippableTheory]
1134+
[ParameterAttributeData]
1135+
public void UpdateOne_should_match_document_of_right_type(
1136+
[Values(false, true)] bool upsert,
1137+
[Values(false, true)] bool async)
1138+
{
1139+
RequireServer.Check().Supports(Feature.UpdateWithAggregationPipeline);
1140+
1141+
string filter = "{ PropA : 4 }";
1142+
1143+
var subject = CreateSubject();
1144+
1145+
var pipeline = new EmptyPipelineDefinition<B>()
1146+
.AppendStage<B, B, B>(
1147+
new BsonDocument
1148+
{
1149+
{ "$set", new BsonDocument(nameof(B.PropA), "Pipeline") }
1150+
})
1151+
.AppendStage<B, B, B>(
1152+
new BsonDocument
1153+
{
1154+
{ "$unset", nameof(B.PropB) }
1155+
});
1156+
1157+
var updateOptions = new UpdateOptions { IsUpsert = upsert };
1158+
var result = async
1159+
? subject.UpdateOneAsync(filter, pipeline, updateOptions).GetAwaiter().GetResult()
1160+
: subject.UpdateOne(filter, pipeline, updateOptions);
1161+
1162+
result.MatchedCount.Should().Be(1);
1163+
result.ModifiedCount.Should().Be(1);
1164+
1165+
var updateQuery = _eventsCapturer.Events.OfType<CommandStartedEvent>().Last().Command["updates"];
1166+
var expectedUpdateDocument = BsonDocument.Parse($@"
1167+
{{
1168+
""q"" :
1169+
{{
1170+
""_t"" : ""B"",
1171+
""PropA"" : 4
1172+
}},
1173+
""u"" :
1174+
[
1175+
{{
1176+
""$set"" : {{ ""PropA"" : ""Pipeline"" }}
1177+
}},
1178+
{{ ""$unset"" : ""PropB"" }},
1179+
{{
1180+
""$set"" :
1181+
{{
1182+
""_t"" :
1183+
{{
1184+
""$cond"" : [{{ ""$eq"" : [{{ ""$type"" : ""$_id"" }}, ""missing""] }},
1185+
[""A"", ""B""],
1186+
""$_t""]
1187+
}}
1188+
}}
1189+
}}
1190+
],
1191+
""upsert"" : true
1192+
}}");
1193+
if (!upsert)
1194+
{
1195+
expectedUpdateDocument.RemoveAt(expectedUpdateDocument.ElementCount - 1); // remove upsert
1196+
var uContent = expectedUpdateDocument["u"].AsBsonArray;
1197+
uContent.RemoveAt(uContent.Count - 1);
1198+
}
1199+
updateQuery.Should().Be(new BsonArray { expectedUpdateDocument });
1200+
}
1201+
11221202
private IMongoCollection<B> CreateSubject()
11231203
{
11241204
return _rootCollection.OfType<B>();
11251205
}
11261206

1207+
public void Dispose() => _client?.Dispose();
1208+
11271209
[BsonDiscriminator(RootClass = true)]
11281210
[BsonKnownTypes(typeof(B), typeof(C))]
11291211
public class A

0 commit comments

Comments
 (0)