Skip to content

Commit e7084d4

Browse files
committed
CSHARP-4684: Provide a way for applications to see what MQL was executed for a LINQ query.
1 parent e8fe884 commit e7084d4

File tree

11 files changed

+150
-91
lines changed

11 files changed

+150
-91
lines changed

src/MongoDB.Driver/Linq/IMongoQueryProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System.Linq.Expressions;
1818
using System.Threading;
1919
using System.Threading.Tasks;
20+
using MongoDB.Bson;
2021
using MongoDB.Bson.Serialization;
2122

2223
namespace MongoDB.Driver.Linq
@@ -31,6 +32,11 @@ internal interface IMongoQueryProvider : IQueryProvider
3132
/// </summary>
3233
CollectionNamespace CollectionNamespace { get; }
3334

35+
/// <summary>
36+
/// Gets the most recently logged stages.
37+
/// </summary>
38+
BsonDocument[] LoggedStages { get; }
39+
3440
/// <summary>
3541
/// Gets the pipeline input serializer (the DocumentSerializer for collection queries and NoPipelineInputSerializer for database queries).
3642
/// </summary>

src/MongoDB.Driver/Linq/IMongoQueryable.cs

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

1616
using System.Linq;
17+
using MongoDB.Bson;
1718

1819
namespace MongoDB.Driver.Linq
1920
{
@@ -22,6 +23,11 @@ namespace MongoDB.Driver.Linq
2223
/// </summary>
2324
public interface IMongoQueryable : IQueryable
2425
{
26+
/// <summary>
27+
/// Gets the pipeline stages that were logged when the queryable was executed.
28+
/// </summary>
29+
BsonDocument[] LoggedStages { get; }
30+
2531
/// <summary>
2632
/// Gets the execution model.
2733
/// </summary>
@@ -58,6 +64,5 @@ public interface IMongoQueryable<T> : IMongoQueryable, IQueryable<T>, IAsyncCurs
5864
/// </typeparam>
5965
public interface IOrderedMongoQueryable<T> : IMongoQueryable<T>, IOrderedQueryable<T>
6066
{
61-
6267
}
6368
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using System.Reflection;
2020
using System.Threading;
2121
using System.Threading.Tasks;
22+
using MongoDB.Bson;
2223
using MongoDB.Bson.Serialization;
2324
using MongoDB.Driver.Core.Misc;
2425
using MongoDB.Driver.Linq.Linq2Implementation.Processors;
@@ -43,6 +44,9 @@ public MongoQueryProviderImpl(IMongoCollection<TDocument> collection, IClientSes
4344

4445
public CollectionNamespace CollectionNamespace => _collection.CollectionNamespace;
4546

47+
public BsonDocument[] LoggedStages =>
48+
throw new InvalidOperationException($"The {nameof(LoggedStages)} property is only valid when using LINQ3.");
49+
4650
public IBsonSerializer PipelineInputSerializer => _collection.DocumentSerializer;
4751

4852
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
using System.Linq.Expressions;
2020
using System.Threading;
2121
using System.Threading.Tasks;
22+
using MongoDB.Bson;
2223
using MongoDB.Driver.Core.Misc;
23-
using MongoDB.Driver.Linq;
2424

2525
namespace MongoDB.Driver.Linq.Linq2Implementation
2626
{
@@ -51,6 +51,8 @@ public Expression Expression
5151
get { return _expression; }
5252
}
5353

54+
public BsonDocument[] LoggedStages => _queryProvider.LoggedStages;
55+
5456
public IQueryProvider Provider
5557
{
5658
get { return _queryProvider; }

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

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

2526
namespace MongoDB.Driver.Linq.Linq3Implementation
@@ -54,6 +55,8 @@ public MongoQuery(MongoQueryProvider<TDocument> provider, Expression expression)
5455

5556
public Expression Expression => _expression;
5657

58+
public BsonDocument[] LoggedStages => _provider.LoggedStages;
59+
5760
public MongoQueryProvider<TDocument> Provider => _provider;
5861

5962
IQueryProvider IQueryable.Provider => _provider;
@@ -62,13 +65,13 @@ public MongoQuery(MongoQueryProvider<TDocument> provider, Expression expression)
6265
public override IAsyncCursor<TOutput> Execute()
6366
{
6467
var executableQuery = ExpressionToExecutableQueryTranslator.Translate<TDocument, TOutput>(_provider, _expression);
65-
return _provider.Execute(executableQuery);
68+
return _provider.Execute(executableQuery, CancellationToken.None);
6669
}
6770

6871
public override Task<IAsyncCursor<TOutput>> ExecuteAsync()
6972
{
7073
var executableQuery = ExpressionToExecutableQueryTranslator.Translate<TDocument, TOutput>(_provider, _expression);
71-
return _provider.ExecuteAsync(executableQuery);
74+
return _provider.ExecuteAsync(executableQuery, CancellationToken.None);
7275
}
7376

7477
public IEnumerator<TOutput> GetEnumerator()

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

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ protected MongoQueryProvider(
4343

4444
// public properties
4545
public abstract CollectionNamespace CollectionNamespace { get; }
46+
public abstract BsonDocument[] LoggedStages { get; }
4647
public AggregateOptions Options => _options;
4748
public abstract IBsonSerializer PipelineInputSerializer { get; }
4849
public IClientSessionHandle Session => _session;
@@ -54,15 +55,14 @@ protected MongoQueryProvider(
5455
public abstract object Execute(Expression expression);
5556
public abstract TResult Execute<TResult>(Expression expression);
5657
public abstract Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken);
57-
public abstract BsonDocument[] GetMostRecentPipelineStages();
5858
}
5959

6060
internal sealed class MongoQueryProvider<TDocument> : MongoQueryProvider
6161
{
6262
// private fields
6363
private readonly IMongoCollection<TDocument> _collection;
6464
private readonly IMongoDatabase _database;
65-
private ExecutableQuery<TDocument> _mostRecentExecutableQuery;
65+
private ExecutableQuery<TDocument> _executedQuery;
6666

6767
// constructors
6868
public MongoQueryProvider(
@@ -87,6 +87,7 @@ public MongoQueryProvider(
8787
public IMongoCollection<TDocument> Collection => _collection;
8888
public override CollectionNamespace CollectionNamespace => _collection == null ? null : _collection.CollectionNamespace;
8989
public IMongoDatabase Database => _database;
90+
public override BsonDocument[] LoggedStages => _executedQuery?.LoggedStages;
9091
public override IBsonSerializer PipelineInputSerializer => _collection == null ? NoPipelineInputSerializer.Instance : _collection.DocumentSerializer;
9192

9293
// public methods
@@ -115,17 +116,12 @@ public override object Execute(Expression expression)
115116
public override TResult Execute<TResult>(Expression expression)
116117
{
117118
var executableQuery = ExpressionToExecutableQueryTranslator.TranslateScalar<TDocument, TResult>(this, expression);
118-
return Execute(executableQuery);
119-
}
120-
121-
public TResult Execute<TResult>(ExecutableQuery<TDocument, TResult> executableQuery)
122-
{
123119
return Execute(executableQuery, CancellationToken.None);
124120
}
125121

126122
public TResult Execute<TResult>(ExecutableQuery<TDocument, TResult> executableQuery, CancellationToken cancellationToken)
127123
{
128-
_mostRecentExecutableQuery = executableQuery;
124+
_executedQuery = executableQuery;
129125
return executableQuery.Execute(_session, cancellationToken);
130126
}
131127

@@ -135,22 +131,10 @@ public override Task<TResult> ExecuteAsync<TResult>(Expression expression, Cance
135131
return ExecuteAsync(executableQuery, cancellationToken);
136132
}
137133

138-
public Task<TResult> ExecuteAsync<TResult>(ExecutableQuery<TDocument, TResult> executableQuery)
139-
{
140-
return ExecuteAsync(executableQuery, CancellationToken.None);
141-
}
142-
143134
public Task<TResult> ExecuteAsync<TResult>(ExecutableQuery<TDocument, TResult> executableQuery, CancellationToken cancellationToken)
144135
{
145-
_mostRecentExecutableQuery = executableQuery;
136+
_executedQuery = executableQuery;
146137
return executableQuery.ExecuteAsync(_session, cancellationToken);
147138
}
148-
149-
public override BsonDocument[] GetMostRecentPipelineStages()
150-
{
151-
var pipeline = _mostRecentExecutableQuery.Pipeline;
152-
var renderedPipeline = (BsonArray)pipeline.Render();
153-
return renderedPipeline.Cast<BsonDocument>().ToArray();
154-
}
155139
}
156140
}

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

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,17 @@ public static ExecutableQuery<TDocument, TOutput, TResult> Create<TDocument, TOu
3939
AstPipeline unoptimizedPipeline,
4040
IExecutableQueryFinalizer<TOutput, TResult> finalizer)
4141
{
42-
var pipeline = AstPipelineOptimizer.Optimize(unoptimizedPipeline);
42+
var optimizedPipeline = AstPipelineOptimizer.Optimize(unoptimizedPipeline);
4343
return provider.Collection == null ?
44-
new ExecutableQuery<TDocument, TOutput, TResult>(provider.Database, provider.Options, unoptimizedPipeline, pipeline, finalizer) :
45-
new ExecutableQuery<TDocument, TOutput, TResult>(provider.Collection, provider.Options, unoptimizedPipeline, pipeline, finalizer);
44+
new ExecutableQuery<TDocument, TOutput, TResult>(provider.Database, provider.Options, optimizedPipeline, finalizer) :
45+
new ExecutableQuery<TDocument, TOutput, TResult>(provider.Collection, provider.Options, optimizedPipeline, finalizer);
4646
}
4747
}
4848

4949
internal abstract class ExecutableQuery<TDocument>
5050
{
51+
public abstract BsonDocument[] LoggedStages { get; }
5152
public abstract AstPipeline Pipeline { get; }
52-
public abstract AstPipeline UnoptimizedPipeline { get; }
5353
}
5454

5555
internal abstract class ExecutableQuery<TDocument, TResult> : ExecutableQuery<TDocument>
@@ -64,71 +64,69 @@ internal class ExecutableQuery<TDocument, TOutput, TResult> : ExecutableQuery<TD
6464
private readonly IMongoCollection<TDocument> _collection;
6565
private readonly IMongoDatabase _database;
6666
private readonly IExecutableQueryFinalizer<TOutput, TResult> _finalizer;
67+
private BsonDocument[] _loggedStages = null;
6768
private readonly AggregateOptions _options;
6869
private readonly AstPipeline _pipeline;
69-
private readonly AstPipeline _unoptimizedPipeline;
7070

7171
// constructors
7272
public ExecutableQuery(
7373
IMongoCollection<TDocument> collection,
7474
AggregateOptions options,
75-
AstPipeline unoptimizedPipeline,
7675
AstPipeline pipeline,
7776
IExecutableQueryFinalizer<TOutput, TResult> finalizer)
78-
: this(options, unoptimizedPipeline, pipeline, finalizer)
77+
: this(options, pipeline, finalizer)
7978
{
8079
_collection = Ensure.IsNotNull(collection, nameof(collection));
8180
}
8281

8382
public ExecutableQuery(
8483
IMongoDatabase database,
8584
AggregateOptions options,
86-
AstPipeline unoptimizedPipeline,
8785
AstPipeline pipeline,
8886
IExecutableQueryFinalizer<TOutput, TResult> finalizer)
89-
: this(options, unoptimizedPipeline, pipeline, finalizer)
87+
: this(options, pipeline, finalizer)
9088
{
9189
_database = Ensure.IsNotNull(database, nameof(database));
9290
}
9391

9492
private ExecutableQuery(
9593
AggregateOptions options,
96-
AstPipeline unoptimizedPipeline,
9794
AstPipeline pipeline,
9895
IExecutableQueryFinalizer<TOutput, TResult> finalizer)
9996
{
10097
_options = options;
101-
_unoptimizedPipeline = unoptimizedPipeline;
10298
_pipeline = pipeline;
10399
_finalizer = finalizer;
104100
}
105101

106102
// public properties
103+
public override BsonDocument[] LoggedStages => _loggedStages;
107104
public override AstPipeline Pipeline => _pipeline;
108-
public override AstPipeline UnoptimizedPipeline => _unoptimizedPipeline;
109105

110106
// public methods
111107
public override TResult Execute(IClientSessionHandle session, CancellationToken cancellationToken)
112108
{
109+
_loggedStages = RenderPipeline();
113110
var cursor = (_collection, session) switch
114111
{
115-
(null, null) => _database.Aggregate(CreateDatabasePipelineDefinition(), _options, cancellationToken),
116-
(null, _) => _database.Aggregate(session, CreateDatabasePipelineDefinition(), _options, cancellationToken),
117-
(_, null) => _collection.Aggregate(CreateCollectionPipelineDefinition(), _options, cancellationToken),
118-
(_, _) => _collection.Aggregate(session, CreateCollectionPipelineDefinition(), _options, cancellationToken)
112+
(null, null) => _database.Aggregate(CreateDatabasePipelineDefinition(_loggedStages), _options, cancellationToken),
113+
(null, _) => _database.Aggregate(session, CreateDatabasePipelineDefinition(_loggedStages), _options, cancellationToken),
114+
(_, null) => _collection.Aggregate(CreateCollectionPipelineDefinition(_loggedStages), _options, cancellationToken),
115+
(_, _) => _collection.Aggregate(session, CreateCollectionPipelineDefinition(_loggedStages), _options, cancellationToken)
119116
};
120117

121118
return _finalizer.Finalize(cursor, cancellationToken);
122119
}
123120

124121
public override async Task<TResult> ExecuteAsync(IClientSessionHandle session, CancellationToken cancellationToken)
125122
{
123+
_loggedStages = RenderPipeline();
126124
var cursor = (_collection, session) switch
127125
{
128-
(null, null) => await _database.AggregateAsync(CreateDatabasePipelineDefinition(), _options, cancellationToken).ConfigureAwait(false),
129-
(null, _) => await _database.AggregateAsync(session, CreateDatabasePipelineDefinition(), _options, cancellationToken).ConfigureAwait(false),
130-
(_, null) => await _collection.AggregateAsync(CreateCollectionPipelineDefinition(), _options, cancellationToken).ConfigureAwait(false),
131-
(_, _) => await _collection.AggregateAsync(session, CreateCollectionPipelineDefinition(), _options, cancellationToken).ConfigureAwait(false)
126+
(null, null) => await _database.AggregateAsync(CreateDatabasePipelineDefinition(_loggedStages), _options, cancellationToken).ConfigureAwait(false),
127+
(null, _) => await _database.AggregateAsync(session, CreateDatabasePipelineDefinition(_loggedStages), _options, cancellationToken).ConfigureAwait(false),
128+
(_, null) => await _collection.AggregateAsync(CreateCollectionPipelineDefinition(_loggedStages), _options, cancellationToken).ConfigureAwait(false),
129+
(_, _) => await _collection.AggregateAsync(session, CreateCollectionPipelineDefinition(_loggedStages), _options, cancellationToken).ConfigureAwait(false)
132130
};
133131

134132
return await _finalizer.FinalizeAsync(cursor, cancellationToken).ConfigureAwait(false);
@@ -141,16 +139,19 @@ public override string ToString()
141139
}
142140

143141
// private methods
144-
private BsonDocumentStagePipelineDefinition<TDocument, TOutput> CreateCollectionPipelineDefinition()
142+
private BsonDocumentStagePipelineDefinition<TDocument, TOutput> CreateCollectionPipelineDefinition(BsonDocument[] stages)
145143
{
146-
var stages = _pipeline.Stages.Select(s => (BsonDocument)s.Render());
147144
return new BsonDocumentStagePipelineDefinition<TDocument, TOutput>(stages, (IBsonSerializer<TOutput>)_pipeline.OutputSerializer);
148145
}
149146

150-
private BsonDocumentStagePipelineDefinition<NoPipelineInput, TOutput> CreateDatabasePipelineDefinition()
147+
private BsonDocumentStagePipelineDefinition<NoPipelineInput, TOutput> CreateDatabasePipelineDefinition(BsonDocument[] stages)
151148
{
152-
var stages = _pipeline.Stages.Select(s => (BsonDocument)s.Render());
153149
return new BsonDocumentStagePipelineDefinition<NoPipelineInput, TOutput>(stages, (IBsonSerializer<TOutput>)_pipeline.OutputSerializer);
154150
}
151+
152+
private BsonDocument[] RenderPipeline()
153+
{
154+
return _pipeline.Render().AsBsonArray.Cast<BsonDocument>().ToArray();
155+
}
155156
}
156157
}

tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp2134Tests.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
using System.Collections.Generic;
1717
using System.Linq;
1818
using FluentAssertions;
19-
using MongoDB.Driver.Linq.Linq3Implementation;
2019
using Xunit;
2120

2221
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
@@ -26,21 +25,18 @@ public class CSharp2134Tests : Linq3IntegrationTest
2625
[Theory]
2726
[InlineData(false, true)]
2827
[InlineData(true, false)]
29-
public void Projection_of_ArrayOfDocuments_dictionary_keys_and_values_should_work(bool includeNullId, bool expectedResult)
28+
public void All_with_predicate_should_work(bool includeNullId, bool expectedResult)
3029
{
3130
var collection = CreateCollection(includeNullId);
32-
3331
var queryable = collection.AsQueryable();
34-
var result = queryable.All(x => x.Id != null);
3532

36-
var provider = (MongoQueryProvider)queryable.Provider;
37-
var stages = provider.GetMostRecentPipelineStages();
33+
var result = queryable.All(x => x.Id != null); // AllAsync is not implemented
34+
3835
AssertStages(
39-
stages,
36+
queryable.LoggedStages,
4037
"{ $match : { _id : null } }",
4138
"{ $limit : 1 }",
4239
"{ $project : { _id : 0, _v : null } }");
43-
4440
result.Should().Be(expectedResult);
4541
}
4642

0 commit comments

Comments
 (0)