Skip to content

Commit 32835bf

Browse files
committed
CSHARP-4339: Add support for $documents in LINQ.
1 parent 9bdc919 commit 32835bf

File tree

22 files changed

+394
-30
lines changed

22 files changed

+394
-30
lines changed

src/MongoDB.Driver/IMongoDatabaseExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
using System.Threading.Tasks;
1818
using MongoDB.Bson;
1919
using MongoDB.Driver.Core.Misc;
20-
using MongoDB.Driver.Core.Operations;
2120
using MongoDB.Driver.Linq;
2221

2322
namespace MongoDB.Driver

src/MongoDB.Driver/Linq/IMongoQueryProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ internal interface IMongoQueryProvider : IQueryProvider
3232
CollectionNamespace CollectionNamespace { get; }
3333

3434
/// <summary>
35-
/// Gets the collection document serializer.
35+
/// Gets the pipeline input serializer (the DocumentSerializer for collection queries and NoPipelineInputSerializer for database queries).
3636
/// </summary>
37-
IBsonSerializer CollectionDocumentSerializer { get; }
37+
IBsonSerializer PipelineInputSerializer { get; }
3838

3939
/// <summary>
4040
/// Gets the execution model.

src/MongoDB.Driver/Linq/Linq2Implementation/MongoQueryProviderImpl.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
using System.Threading.Tasks;
2222
using MongoDB.Bson.Serialization;
2323
using MongoDB.Driver.Core.Misc;
24-
using MongoDB.Driver.Linq;
2524
using MongoDB.Driver.Linq.Linq2Implementation.Processors;
2625
using MongoDB.Driver.Linq.Linq2Implementation.Processors.Pipeline;
2726
using MongoDB.Driver.Linq.Linq2Implementation.Translators;
@@ -44,7 +43,7 @@ public MongoQueryProviderImpl(IMongoCollection<TDocument> collection, IClientSes
4443

4544
public CollectionNamespace CollectionNamespace => _collection.CollectionNamespace;
4645

47-
public IBsonSerializer CollectionDocumentSerializer => _collection.DocumentSerializer;
46+
public IBsonSerializer PipelineInputSerializer => _collection.DocumentSerializer;
4847

4948
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
5049
{

src/MongoDB.Driver/Linq/Linq2Implementation/Processors/Pipeline/PipelineBinder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ protected override Expression BindNonMethodCall(Expression node)
8080
var queryable = (IMongoQueryable)((ConstantExpression)node).Value;
8181
var provider = (IMongoQueryProvider)queryable.Provider;
8282
return new PipelineExpression(
83-
new CollectionExpression(provider.CollectionNamespace, provider.CollectionDocumentSerializer),
84-
new DocumentExpression(provider.CollectionDocumentSerializer));
83+
new CollectionExpression(provider.CollectionNamespace, provider.PipelineInputSerializer),
84+
new DocumentExpression(provider.PipelineInputSerializer));
8585
}
8686

8787
var message = string.Format("The expression tree is not supported: {0}",

src/MongoDB.Driver/Linq/Linq2Implementation/Processors/SerializationBinder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ protected override Expression VisitConstant(ConstantExpression node)
9696
var provider = (IMongoQueryProvider)queryable.Provider;
9797
return new CollectionExpression(
9898
provider.CollectionNamespace,
99-
provider.CollectionDocumentSerializer);
99+
provider.PipelineInputSerializer);
100100
}
101101

102102
return base.VisitConstant(node);

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/AstNodeType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ internal enum AstNodeType
5353
DateTruncExpression,
5454
DensifyStage,
5555
DerivativeOrIntegralWindowExpression,
56+
DocumentsStage,
5657
ElemMatchFilterOperation,
5758
ExistsFilterOperation,
5859
ExponentialMovingAverageWindowExpression,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 MongoDB.Bson;
17+
using MongoDB.Driver.Core.Misc;
18+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
19+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors;
20+
21+
namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Stages
22+
{
23+
internal sealed class AstDocumentsStage : AstStage
24+
{
25+
private readonly AstExpression _documents;
26+
27+
public AstDocumentsStage(AstExpression documents)
28+
{
29+
_documents = Ensure.IsNotNull(documents, nameof(documents));
30+
}
31+
32+
public new AstExpression Documents => _documents;
33+
public override AstNodeType NodeType => AstNodeType.DocumentsStage;
34+
35+
public override AstNode Accept(AstNodeVisitor visitor)
36+
{
37+
return visitor.VisitDocumentsStage(this);
38+
}
39+
40+
public override BsonValue Render()
41+
{
42+
return new BsonDocument("$documents", _documents.Render());
43+
}
44+
45+
public AstDocumentsStage Update(AstExpression documents)
46+
{
47+
if (documents == _documents)
48+
{
49+
return this;
50+
}
51+
52+
return new AstDocumentsStage(documents);
53+
}
54+
}
55+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstStage.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ public static AstStage Densify(
7777
return new AstDensifyStage(fieldPath, range, partitionByFieldPaths);
7878
}
7979

80+
public static AstStage Documents(
81+
AstExpression documents)
82+
{
83+
return new AstDocumentsStage(documents);
84+
}
85+
8086
public static AstStage Facet(IEnumerable<AstFacetStageFacet> facets)
8187
{
8288
return new AstFacetStage(facets);

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Visitors/AstNodeVisitor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,11 @@ public virtual AstNode VisitDerivativeOrIntegralWindowExpression(AstDerivativeOr
299299
return node.Update(node.Operator, VisitAndConvert(node.Arg), node.Unit, node.Window);
300300
}
301301

302+
public virtual AstNode VisitDocumentsStage(AstDocumentsStage node)
303+
{
304+
return node.Update(VisitAndConvert(node.Documents));
305+
}
306+
302307
public virtual AstNode VisitElemMatchFilterOperation(AstElemMatchFilterOperation node)
303308
{
304309
return node.Update(VisitAndConvert(node.Filter));

src/MongoDB.Driver/Linq/Linq3Implementation/ExtensionMethods/ExpressionExtensions.cs

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

16-
using System.Linq;
1716
using System.Linq.Expressions;
1817
using MongoDB.Bson.Serialization;
1918

@@ -24,14 +23,15 @@ internal static class ExpressionExtensions
2423
public static (string CollectionName, IBsonSerializer DocumentSerializer) GetCollectionInfo(this Expression innerExpression, Expression containerExpression)
2524
{
2625
if (innerExpression is ConstantExpression constantExpression &&
27-
constantExpression.Value is IQueryable queryable &&
28-
queryable.Provider is MongoQueryProvider queryProvider)
26+
constantExpression.Value is IMongoQueryable mongoQueryable &&
27+
mongoQueryable.Provider is IMongoQueryProvider mongoQueryProvider &&
28+
mongoQueryProvider.CollectionNamespace != null)
2929
{
30-
return (queryProvider.CollectionNamespace.CollectionName, queryProvider.CollectionDocumentSerializer);
30+
return (mongoQueryProvider.CollectionNamespace.CollectionName, mongoQueryProvider.PipelineInputSerializer);
3131
}
3232

33-
var message = $"Expression inner must be a MongoDB queryable representing a collection: {innerExpression} in {containerExpression}.";
34-
throw new ExpressionNotSupportedException(message);
33+
var message = $"inner expression is not an IMongoQueryable representing a collection";
34+
throw new ExpressionNotSupportedException(innerExpression, containerExpression, because: message);
3535
}
3636

3737
public static TValue GetConstantValue<TValue>(this Expression expression, Expression containingExpression)

src/MongoDB.Driver/Linq/Linq3Implementation/GroupExpressionStageDefinitions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,7 @@ public PseudoQueryProvider(IBsonSerializer inputSerializer)
123123
}
124124

125125
public CollectionNamespace CollectionNamespace => throw new NotImplementedException();
126-
127-
public IBsonSerializer CollectionDocumentSerializer => _inputSerializer;
126+
public IBsonSerializer PipelineInputSerializer => _inputSerializer;
128127

129128
public IQueryable CreateQuery(Expression expression) => throw new NotImplementedException();
130129
public IQueryable<TElement> CreateQuery<TElement>(Expression expression) => throw new NotImplementedException();

src/MongoDB.Driver/Linq/Linq3Implementation/LinqProviderAdapterV3.cs

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

1616
using System;
17-
using System.Collections.ObjectModel;
1817
using System.Linq;
1918
using System.Linq.Expressions;
2019
using MongoDB.Bson;

src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Threading.Tasks;
2121
using MongoDB.Bson;
2222
using MongoDB.Bson.Serialization;
23+
using MongoDB.Driver.Core.Misc;
2324
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToExecutableQueryTranslators;
2425
using MongoDB.Driver.Support;
2526

@@ -41,9 +42,9 @@ protected MongoQueryProvider(
4142
}
4243

4344
// public properties
44-
public abstract IBsonSerializer CollectionDocumentSerializer { get; }
4545
public abstract CollectionNamespace CollectionNamespace { get; }
4646
public AggregateOptions Options => _options;
47+
public abstract IBsonSerializer PipelineInputSerializer { get; }
4748
public IClientSessionHandle Session => _session;
4849

4950
// public methods
@@ -70,7 +71,7 @@ public MongoQueryProvider(
7071
AggregateOptions options)
7172
: base(session, options)
7273
{
73-
_collection = collection;
74+
_collection = Ensure.IsNotNull(collection, nameof(collection));
7475
}
7576

7677
public MongoQueryProvider(
@@ -79,14 +80,14 @@ public MongoQueryProvider(
7980
AggregateOptions options)
8081
: base(session, options)
8182
{
82-
_database = database;
83+
_database = Ensure.IsNotNull(database, nameof(database));
8384
}
8485

8586
// public properties
8687
public IMongoCollection<TDocument> Collection => _collection;
87-
public override CollectionNamespace CollectionNamespace => _collection.CollectionNamespace;
88-
public override IBsonSerializer CollectionDocumentSerializer => _collection.DocumentSerializer;
88+
public override CollectionNamespace CollectionNamespace => _collection == null ? null : _collection.CollectionNamespace;
8989
public IMongoDatabase Database => _database;
90+
public override IBsonSerializer PipelineInputSerializer => _collection == null ? NoPipelineInputSerializer.Instance : _collection.DocumentSerializer;
9091

9192
// public methods
9293
public override IQueryable CreateQuery(Expression expression)

src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/MongoQueryableMethod.cs

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

1616
using System;
17+
using System.Collections.Generic;
1718
using System.Linq.Expressions;
1819
using System.Reflection;
1920
using System.Threading;
@@ -50,6 +51,8 @@ internal static class MongoQueryableMethod
5051
private static readonly MethodInfo __countAsync;
5152
private static readonly MethodInfo __countWithPredicateAsync;
5253
private static readonly MethodInfo __densifyWithArrayPartitionByFields;
54+
private static readonly MethodInfo __documents;
55+
private static readonly MethodInfo __documentsWithSerializer;
5356
private static readonly MethodInfo __firstAsync;
5457
private static readonly MethodInfo __firstOrDefaultAsync;
5558
private static readonly MethodInfo __firstOrDefaultWithPredicateAsync;
@@ -195,6 +198,8 @@ static MongoQueryableMethod()
195198
__countAsync = ReflectionInfo.Method((IMongoQueryable<object> source, CancellationToken cancellationToken) => source.CountAsync(cancellationToken));
196199
__countWithPredicateAsync = ReflectionInfo.Method((IMongoQueryable<object> source, Expression<Func<object, bool>> predicate, CancellationToken cancellationToken) => source.CountAsync(predicate, cancellationToken));
197200
__densifyWithArrayPartitionByFields = ReflectionInfo.Method((IMongoQueryable<object> source, Expression<Func<object, object>> field, DensifyRange range, Expression<Func<object, object>>[] partitionByFields) => source.Densify(field, range, partitionByFields));
201+
__documents = ReflectionInfo.Method((IMongoQueryable<NoPipelineInput> source, object[] documents) => source.Documents(documents));
202+
__documentsWithSerializer = ReflectionInfo.Method((IMongoQueryable<NoPipelineInput> source, IEnumerable<object> documents, IBsonSerializer<object> serializer) => source.Documents(documents, serializer));
198203
__firstAsync = ReflectionInfo.Method((IMongoQueryable<object> source, CancellationToken cancellationToken) => source.FirstAsync(cancellationToken));
199204
__firstOrDefaultAsync = ReflectionInfo.Method((IMongoQueryable<object> source, CancellationToken cancellationToken) => source.FirstOrDefaultAsync(cancellationToken));
200205
__firstOrDefaultWithPredicateAsync = ReflectionInfo.Method((IMongoQueryable<object> source, Expression<Func<object, bool>> predicate, CancellationToken cancellationToken) => source.FirstOrDefaultAsync(predicate, cancellationToken));
@@ -339,6 +344,8 @@ static MongoQueryableMethod()
339344
public static MethodInfo CountAsync => __countAsync;
340345
public static MethodInfo CountWithPredicateAsync => __countWithPredicateAsync;
341346
public static MethodInfo DensifyWithArrayPartitionByFields => __densifyWithArrayPartitionByFields;
347+
public static MethodInfo Documents => __documents;
348+
public static MethodInfo DocumentsWithSerializer => __documentsWithSerializer;
342349
public static MethodInfo FirstAsync => __firstAsync;
343350
public static MethodInfo FirstOrDefaultAsync => __firstOrDefaultAsync;
344351
public static MethodInfo FirstOrDefaultWithPredicateAsync => __firstOrDefaultWithPredicateAsync;

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExecutableQuery.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using System.Threading.Tasks;
1919
using MongoDB.Bson;
2020
using MongoDB.Bson.Serialization;
21+
using MongoDB.Driver.Core.Misc;
2122
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
2223
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Optimizers;
2324

@@ -76,7 +77,7 @@ public ExecutableQuery(
7677
IExecutableQueryFinalizer<TOutput, TResult> finalizer)
7778
: this(options, unoptimizedPipeline, pipeline, finalizer)
7879
{
79-
_collection = collection;
80+
_collection = Ensure.IsNotNull(collection, nameof(collection));
8081
}
8182

8283
public ExecutableQuery(
@@ -87,7 +88,7 @@ public ExecutableQuery(
8788
IExecutableQueryFinalizer<TOutput, TResult> finalizer)
8889
: this(options, unoptimizedPipeline, pipeline, finalizer)
8990
{
90-
_database = database;
91+
_database = Ensure.IsNotNull(database, nameof(database));
9192
}
9293

9394
private ExecutableQuery(
@@ -135,6 +136,7 @@ public override async Task<TResult> ExecuteAsync(IClientSessionHandle session, C
135136

136137
public override string ToString()
137138
{
139+
var x = (object)_database?.DatabaseNamespace ?? _collection.CollectionNamespace;
138140
return $"{(_collection == null ? _database.DatabaseNamespace : _collection.CollectionNamespace)}.Aggregate({_pipeline})";
139141
}
140142

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExpressionToExecutableQueryTranslator.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
using System.Linq.Expressions;
1717
using System.Threading;
1818
using System.Threading.Tasks;
19-
using MongoDB.Bson.Serialization;
2019
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
21-
using MongoDB.Driver.Linq.Linq3Implementation.Serializers.KnownSerializers;
2220
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToPipelineTranslators;
2321

2422
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToExecutableQueryTranslators
@@ -30,7 +28,7 @@ public static ExecutableQuery<TDocument, IAsyncCursor<TOutput>> Translate<TDocum
3028
{
3129
expression = PartialEvaluator.EvaluatePartially(expression);
3230

33-
var context = TranslationContext.Create(expression, provider.CollectionDocumentSerializer);
31+
var context = TranslationContext.Create(expression, provider.PipelineInputSerializer);
3432
var pipeline = ExpressionToPipelineTranslator.Translate(context, expression);
3533

3634
return ExecutableQuery.Create(
@@ -43,7 +41,7 @@ public static ExecutableQuery<TDocument, TResult> TranslateScalar<TDocument, TRe
4341
{
4442
expression = PartialEvaluator.EvaluatePartially(expression);
4543

46-
var context = TranslationContext.Create(expression, provider.CollectionDocumentSerializer);
44+
var context = TranslationContext.Create(expression, provider.PipelineInputSerializer);
4745
var methodCallExpression = (MethodCallExpression)expression;
4846
switch (methodCallExpression.Method.Name)
4947
{

0 commit comments

Comments
 (0)