diff --git a/src/MongoDB.Driver.Core/Core/Operations/FindOneAndDeleteOperation.cs b/src/MongoDB.Driver.Core/Core/Operations/FindOneAndDeleteOperation.cs
index ef02349b8fa..a30817efe41 100644
--- a/src/MongoDB.Driver.Core/Core/Operations/FindOneAndDeleteOperation.cs
+++ b/src/MongoDB.Driver.Core/Core/Operations/FindOneAndDeleteOperation.cs
@@ -127,12 +127,14 @@ public BsonDocument Sort
// methods
internal override BsonDocument CreateCommand(ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber)
{
- var maxWireVersion = connectionDescription.MaxWireVersion;
- if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(maxWireVersion) || (WriteConcern != null && !WriteConcern.IsAcknowledged))
+ var wireVersion = connectionDescription.MaxWireVersion;
+ FindProjectionChecker.ThrowIfAggregationExpressionIsUsedWhenNotSupported(_projection, wireVersion);
+
+ if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(wireVersion) || (WriteConcern != null && !WriteConcern.IsAcknowledged))
{
if (_hint != null)
{
- throw new NotSupportedException($"Server version {WireVersion.GetServerVersionForErrorMessage(maxWireVersion)} does not support hints.");
+ throw new NotSupportedException($"Server version {WireVersion.GetServerVersionForErrorMessage(wireVersion)} does not support hints.");
}
}
diff --git a/src/MongoDB.Driver.Core/Core/Operations/FindOneAndReplaceOperation.cs b/src/MongoDB.Driver.Core/Core/Operations/FindOneAndReplaceOperation.cs
index 3e9497f0609..723bed3ef98 100644
--- a/src/MongoDB.Driver.Core/Core/Operations/FindOneAndReplaceOperation.cs
+++ b/src/MongoDB.Driver.Core/Core/Operations/FindOneAndReplaceOperation.cs
@@ -181,12 +181,14 @@ public BsonDocument Sort
// methods
internal override BsonDocument CreateCommand(ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber)
{
- var maxWireVersion = connectionDescription.MaxWireVersion;
- if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(maxWireVersion) || (WriteConcern != null && !WriteConcern.IsAcknowledged))
+ var wireVersion = connectionDescription.MaxWireVersion;
+ FindProjectionChecker.ThrowIfAggregationExpressionIsUsedWhenNotSupported(_projection, wireVersion);
+
+ if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(wireVersion) || (WriteConcern != null && !WriteConcern.IsAcknowledged))
{
if (_hint != null)
{
- throw new NotSupportedException($"Server version {WireVersion.GetServerVersionForErrorMessage(maxWireVersion)} does not support hints.");
+ throw new NotSupportedException($"Server version {WireVersion.GetServerVersionForErrorMessage(wireVersion)} does not support hints.");
}
}
diff --git a/src/MongoDB.Driver.Core/Core/Operations/FindOneAndUpdateOperation.cs b/src/MongoDB.Driver.Core/Core/Operations/FindOneAndUpdateOperation.cs
index cdb180a061b..ead4db0ff4a 100644
--- a/src/MongoDB.Driver.Core/Core/Operations/FindOneAndUpdateOperation.cs
+++ b/src/MongoDB.Driver.Core/Core/Operations/FindOneAndUpdateOperation.cs
@@ -196,12 +196,14 @@ public BsonValue Update
// methods
internal override BsonDocument CreateCommand(ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber)
{
- var maxWireVersion = connectionDescription.MaxWireVersion;
- if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(maxWireVersion) || (WriteConcern != null && !WriteConcern.IsAcknowledged))
+ var wireVersion = connectionDescription.MaxWireVersion;
+ FindProjectionChecker.ThrowIfAggregationExpressionIsUsedWhenNotSupported(_projection, wireVersion);
+
+ if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(wireVersion) || (WriteConcern != null && !WriteConcern.IsAcknowledged))
{
if (_hint != null)
{
- throw new NotSupportedException($"Server version {WireVersion.GetServerVersionForErrorMessage(maxWireVersion)} does not support hints.");
+ throw new NotSupportedException($"Server version {WireVersion.GetServerVersionForErrorMessage(wireVersion)} does not support hints.");
}
}
diff --git a/src/MongoDB.Driver.Core/Core/Operations/FindOperation.cs b/src/MongoDB.Driver.Core/Core/Operations/FindOperation.cs
index c6d2d0f057c..b2b9a906910 100644
--- a/src/MongoDB.Driver.Core/Core/Operations/FindOperation.cs
+++ b/src/MongoDB.Driver.Core/Core/Operations/FindOperation.cs
@@ -469,6 +469,9 @@ public BsonDocument Sort
///
public BsonDocument CreateCommand(ConnectionDescription connectionDescription, ICoreSession session)
{
+ var wireVersion = connectionDescription.MaxWireVersion;
+ FindProjectionChecker.ThrowIfAggregationExpressionIsUsedWhenNotSupported(_projection, wireVersion);
+
var firstBatchSize = _firstBatchSize ?? (_batchSize > 0 ? _batchSize : null);
var isShardRouter = connectionDescription.HelloResult.ServerType == ServerType.ShardRouter;
diff --git a/src/MongoDB.Driver.Core/Core/Operations/FindProjectionChecker.cs b/src/MongoDB.Driver.Core/Core/Operations/FindProjectionChecker.cs
new file mode 100644
index 00000000000..6728ef7cf38
--- /dev/null
+++ b/src/MongoDB.Driver.Core/Core/Operations/FindProjectionChecker.cs
@@ -0,0 +1,64 @@
+/* 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 MongoDB.Bson;
+using MongoDB.Driver.Core.Misc;
+
+namespace MongoDB.Driver.Core.Operations
+{
+ internal static class FindProjectionChecker
+ {
+ internal static void ThrowIfAggregationExpressionIsUsedWhenNotSupported(BsonDocument projection, int wireVersion)
+ {
+ if (projection == null || Feature.FindProjectionExpressions.IsSupported(wireVersion))
+ {
+ return;
+ }
+
+ foreach (var specification in projection)
+ {
+ ThrowIfAggregationExpressionIsUsed(specification);
+ }
+
+ static void ThrowIfAggregationExpressionIsUsed(BsonElement specification)
+ {
+ if (IsAggregationExpression(specification.Value))
+ {
+ var specificationAsDocument = new BsonDocument(specification);
+ throw new NotSupportedException($"The projection specification {specificationAsDocument} uses an aggregation expression and is not supported with find on servers prior to version 4.4.");
+ }
+ }
+
+ static bool IsAggregationExpression(BsonValue value)
+ {
+ return value.BsonType switch
+ {
+ BsonType.Boolean => false,
+ _ when value.IsNumeric => false,
+ _ when value is BsonDocument documentValue =>
+ documentValue.ElementCount == 1 && documentValue.GetElement(0).Name switch
+ {
+ "$elemMatch" => false,
+ "$meta" => false,
+ "$slice" => false,
+ _ => true,
+ },
+ _ => true
+ };
+ }
+ }
+ }
+}
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4734Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4734Tests.cs
new file mode 100644
index 00000000000..ae2cdeddd51
--- /dev/null
+++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4734Tests.cs
@@ -0,0 +1,224 @@
+/* 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;
+using MongoDB.Driver.Core.Misc;
+using Xunit;
+
+namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
+{
+ public class CSharp4734Tests : Linq3IntegrationTest
+ {
+ [Theory]
+ [InlineData("{ X : false }", "{ _id : 1 }")]
+ [InlineData("{ X : true }", "{ _id : 1, X : 2 }")]
+ [InlineData("{ X : 0 }", "{ _id : 1 }")]
+ [InlineData("{ X : 1 }", "{ _id : 1, X : 2 }")]
+ [InlineData("{ X : -1 }", "{ _id : 1, X : 2 }")]
+ [InlineData("{ X : { $numberLong : 0 } }", "{ _id : 1 }")]
+ [InlineData("{ X : { $numberLong : 1 } }", "{ _id : 1, X : 2 }")]
+ [InlineData("{ X : { $numberLong : -1 } }", "{ _id : 1, X : 2 }")]
+ [InlineData("{ X : 0.0 }", "{ _id : 1 }")]
+ [InlineData("{ X : 1.0 }", "{ _id : 1, X : 2 }")]
+ [InlineData("{ X : -1.0 }", "{ _id : 1, X : 2 }")]
+ public void Find_with_projections_that_should_work_on_all_server_versions(string projection, string expectedResult)
+ {
+ var collection = GetCollection("{ _id : 1, X : 2 }");
+
+ var find = collection
+ .Find("{}")
+ .Project(projection);
+
+ var result = find.Single();
+ result.Should().Be(expectedResult);
+ }
+
+ [Theory]
+ [InlineData("{}", "{ A : { $slice : 2 } }", "{ _id : 1, A : [1, 2] }")]
+ [InlineData("{}", "{ A : { $slice : -2 } }", "{ _id : 1, A : [2, 3] }")]
+ [InlineData("{}", "{ A : { $slice : [1, 2] } }", "{ _id : 1, A : [2, 3] }")]
+ [InlineData("{}", "{ A : { $slice : [-3, 2] } }", "{ _id : 1, A : [1, 2] }")]
+ [InlineData("{}", "{ A : { $elemMatch : { $gt : 1 } } }", "{ _id : 1, A : [2] }")]
+ [InlineData("{ A : { $gt : 1 } }", "{ 'A.$' : true }", "{ _id : 1, A : [2] }")]
+ [InlineData("{ A : { $gt : 1 } }", "{ 'A.$' : 1 }", "{ _id : 1, A : [2] }")]
+ [InlineData("{ A : { $gt : 1 } }", "{ 'A.$' : -1 }", "{ _id : 1, A : [2] }")]
+ [InlineData("{ A : { $gt : 1 } }", "{ 'A.$' : { $numberLong : 1 } }", "{ _id : 1, A : [2] }")]
+ [InlineData("{ A : { $gt : 1 } }", "{ 'A.$' : { $numberLong : -1 } }", "{ _id : 1, A : [2] }")]
+ [InlineData("{ A : { $gt : 1 } }", "{ 'A.$' : 1.0 }", "{ _id : 1, A : [2] }")]
+ [InlineData("{ A : { $gt : 1 } }", "{ 'A.$' : -1.0 }", "{ _id : 1, A : [2] }")]
+ public void Find_with_array_projection_that_should_work_on_all_server_versions(string filter, string projection, string expectedResult)
+ {
+ var collection = GetCollection("{ _id : 1, A : [1, 2, 3] }");
+
+ var find = collection
+ .Find(filter)
+ .Project(projection);
+
+ var result = find.Single();
+ result.Should().BeEquivalentTo(expectedResult); // order of result elements varies by server version
+ }
+
+ [Theory]
+ [InlineData("{ $text : { $search : 'coffee' } }", "{ score : { $meta : 'textScore' } }", "{ _id : 1, subject : 'coffee', score : 1.0 }")]
+ public void Find_with_meta_projection_that_should_work_on_all_server_versions(string filter, string projection, string expectedResult)
+ {
+ var collection = GetCollection("{ _id : 1, subject : 'coffee' }");
+ var keyDefinition = new IndexKeysDefinitionBuilder().Text("subject");
+ var indexModel = new CreateIndexModel(keyDefinition);
+ collection.Indexes.CreateOne(indexModel);
+
+ var find = collection
+ .Find(filter)
+ .Project(projection);
+
+ var result = find.Single();
+ result.Should().Be(expectedResult);
+ }
+
+ [Theory]
+ [InlineData("{ X : 'abc' }", "{ _id : 1, X : 'abc' }")]
+ [InlineData("{ X : '$Y' }", "{ _id : 1, X : 3 }")]
+ [InlineData("{ X : { $add : ['$X', '$Y'] } }", "{ _id : 1, X : 5 }")]
+ [InlineData("{ X : { $literal : true } }", "{ _id : 1, X : true }")]
+ [InlineData("{ X : { $literal : 1 } }", "{ _id : 1, X : 1 }")]
+ [InlineData("{ X : { $literal : { $numberLong : 1 } } }", "{ _id : 1, X : { $numberLong : 1 } }")]
+ [InlineData("{ X : { $literal : 1.0 } }", "{ _id : 1, X : 1.0 }")]
+ public void Find_with_projections_that_should_work_only_on_servers_newer_than_44(string projection, string expectedResult)
+ {
+ var collection = GetCollection("{ _id : 1, X : 2, Y : 3 }");
+
+ var find = collection
+ .Find("{}")
+ .Project(projection);
+
+ var wireVersion = CoreTestConfiguration.MaxWireVersion;
+ if (Feature.FindProjectionExpressions.IsSupported(wireVersion))
+ {
+ var result = find.Single();
+ result.Should().BeEquivalentTo(expectedResult); // order of result elements varies by server version
+ }
+ else
+ {
+ var exception = Record.Exception(() => find.Single());
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("is not supported with find on servers prior to version 4.4.");
+ }
+ }
+
+ [Theory]
+ [InlineData("{ X : { Y : 1 } }", "{ _id : 1, X : { Y : 2 } }")]
+ [InlineData("{ X : { Y : 0 } }", "{ _id : 1, X : { Z : 3 } }")]
+ [InlineData("{ X : { Y : { $numberLong : 1 } } }", "{ _id : 1, X : { Y : 2 } }")]
+ [InlineData("{ X : { Y : 1.0 } }", "{ _id : 1, X : { Y : 2 } }")]
+ public void Find_with_nested_field_projections_that_should_work_only_on_servers_newer_than_44(string projection, string expectedResult)
+ {
+ var collection = GetCollection("{ _id : 1, X : { Y : 2, Z : 3 } }");
+
+ var find = collection
+ .Find("{}")
+ .Project(projection);
+
+ var wireVersion = CoreTestConfiguration.MaxWireVersion;
+ if (Feature.FindProjectionExpressions.IsSupported(wireVersion))
+ {
+ var result = find.Single();
+ result.Should().BeEquivalentTo(expectedResult); // order of result elements varies by server version
+ }
+ else
+ {
+ var exception = Record.Exception(() => find.Single());
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("is not supported with find on servers prior to version 4.4.");
+ }
+ }
+
+ [Fact] // it is sufficient to test only one projection because the rest are tested using Find
+ public void FindOneAndDelete_with_projections_that_should_work_only_on_servers_newer_than_44()
+ {
+ var collection = GetCollection("{ _id : 1, X : 2, Y : 3 }");
+ var filter = "{ _id : 1 }";
+ var options = new FindOneAndDeleteOptions { Projection = "{ Z : '$Y' }" };
+
+ var wireVersion = CoreTestConfiguration.MaxWireVersion;
+ if (Feature.FindProjectionExpressions.IsSupported(wireVersion))
+ {
+ var result = collection.FindOneAndDelete(filter, options);
+ result.Should().BeEquivalentTo("{ _id : 1, Z : 3 }"); // order of result elements varies by server version
+ }
+ else
+ {
+ var exception = Record.Exception(() => collection.FindOneAndDelete(filter, options));
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("is not supported with find on servers prior to version 4.4.");
+ }
+ }
+
+ [Fact] // it is sufficient to test only one projection because the rest are tested using Find
+ public void FindOneAndReplace_with_projections_that_should_work_only_on_servers_newer_than_44()
+ {
+ var collection = GetCollection("{ _id : 1, X : 2, Y : 3 }");
+ var filter = "{ _id : 1 }";
+ var replacement = BsonDocument.Parse("{ _id : 1, X : 4, Y : 5 }");
+ var options = new FindOneAndReplaceOptions { Projection = "{ Z : '$Y' }", ReturnDocument = ReturnDocument.After };
+
+ var wireVersion = CoreTestConfiguration.MaxWireVersion;
+ if (Feature.FindProjectionExpressions.IsSupported(wireVersion))
+ {
+ var result = collection.FindOneAndReplace(filter, replacement, options);
+ result.Should().BeEquivalentTo("{ _id : 1, Z : 5 }"); // order of result elements varies by server version
+ }
+ else
+ {
+ var exception = Record.Exception(() => collection.FindOneAndReplace(filter, replacement, options));
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("is not supported with find on servers prior to version 4.4.");
+ }
+ }
+
+ [Fact] // it is sufficient to test only one projection because the rest are tested using Find
+ public void FindOneAndUpdate_with_projections_that_should_work_only_on_servers_newer_than_44()
+ {
+ var collection = GetCollection("{ _id : 1, X : 2, Y : 3 }");
+ var filter = "{ _id : 1 }";
+ var update = "{ $inc : { Y : 1 } }";
+ var options = new FindOneAndUpdateOptions { Projection = "{ Z : '$Y' }", ReturnDocument = ReturnDocument.After };
+
+ var wireVersion = CoreTestConfiguration.MaxWireVersion;
+ if (Feature.FindProjectionExpressions.IsSupported(wireVersion))
+ {
+ var result = collection.FindOneAndUpdate(filter, update, options);
+ result.Should().BeEquivalentTo("{ _id : 1, Z : 4 }"); // order of result elements varies by server version
+ }
+ else
+ {
+ var exception = Record.Exception(() => collection.FindOneAndUpdate(filter, update, options));
+ exception.Should().BeOfType();
+ exception.Message.Should().Contain("is not supported with find on servers prior to version 4.4.");
+ }
+ }
+
+ private IMongoCollection GetCollection(params string[] documents)
+ {
+ var collection = GetCollection("test");
+ CreateCollection(
+ collection,
+ documents.Select(BsonDocument.Parse));
+ return collection;
+ }
+ }
+}