/* 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.Collections.Generic;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Core.Misc;
namespace MongoDB.Driver
{
///
/// A rendered pipeline.
///
/// The type of the output.
public class RenderedPipelineDefinition
{
private List _documents;
private IBsonSerializer _outputSerializer;
///
/// Initializes a new instance of the class.
///
/// The pipeline.
/// The output serializer.
public RenderedPipelineDefinition(IEnumerable documents, IBsonSerializer outputSerializer)
{
_documents = Ensure.IsNotNull(documents, nameof(documents)).ToList();
_outputSerializer = Ensure.IsNotNull(outputSerializer, nameof(outputSerializer));
}
///
/// Gets the documents.
///
public IList Documents
{
get { return _documents; }
}
///
/// Gets the serializer.
///
public IBsonSerializer OutputSerializer
{
get { return _outputSerializer; }
}
}
///
/// Base class for a pipeline.
///
/// The type of the input.
/// The type of the output.
public abstract class PipelineDefinition
{
///
/// Gets the output serializer.
///
public abstract IBsonSerializer OutputSerializer { get; }
///
/// Gets the stages.
///
public abstract IEnumerable Stages { get; }
///
/// Renders the pipeline.
///
/// The input serializer.
/// The serializer registry.
/// A
public abstract RenderedPipelineDefinition Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry);
///
public override string ToString()
{
var serializerRegistry = BsonSerializer.SerializerRegistry;
var inputSerializer = serializerRegistry.GetSerializer();
return ToString(inputSerializer, serializerRegistry);
}
///
/// Returns a that represents this instance.
///
/// The input serializer.
/// The serializer registry.
///
/// A that represents this instance.
///
public string ToString(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry)
{
var renderedPipeline = Render(inputSerializer, serializerRegistry);
return $"[{string.Join(", ", renderedPipeline.Documents.Select(stage => stage.ToJson()))}]";
}
///
/// Creates a pipeline.
///
/// The stages.
/// The output serializer.
/// A .
public static PipelineDefinition Create(
IEnumerable stages,
IBsonSerializer outputSerializer = null)
{
if (stages == null)
{
return null;
}
return new PipelineStagePipelineDefinition(stages, outputSerializer);
}
///
/// Creates a pipeline.
///
/// The stages.
/// The output serializer.
/// A .
public static PipelineDefinition Create(
IEnumerable stages,
IBsonSerializer outputSerializer = null)
{
if (stages == null)
{
return null;
}
return new BsonDocumentStagePipelineDefinition(stages, outputSerializer);
}
///
/// Creates a pipeline.
///
/// The stages.
/// The output serializer.
/// A .
public static PipelineDefinition Create(
IEnumerable stages,
IBsonSerializer outputSerializer = null)
{
return Create(stages?.Select(s => BsonDocument.Parse(s)), outputSerializer);
}
///
/// Creates a pipeline.
///
/// The stages.
/// A .
public static PipelineDefinition Create(
params BsonDocument[] stages)
{
return Create((IEnumerable)stages);
}
///
/// Creates a pipeline.
///
/// The stages.
/// A .
public static PipelineDefinition Create(
params string[] stages)
{
return Create((IEnumerable)stages);
}
///
/// Performs an implicit conversion from [] to .
///
/// The stages.
///
/// The result of the conversion.
///
public static implicit operator PipelineDefinition(IPipelineStageDefinition[] stages)
{
return Create(stages);
}
///
/// Performs an implicit conversion from to .
///
/// The stages.
///
/// The result of the conversion.
///
public static implicit operator PipelineDefinition(List stages)
{
return Create(stages);
}
///
/// Performs an implicit conversion from [] to .
///
/// The stages.
///
/// The result of the conversion.
///
public static implicit operator PipelineDefinition(BsonDocument[] stages)
{
return Create(stages);
}
///
/// Performs an implicit conversion from to .
///
/// The stages.
///
/// The result of the conversion.
///
public static implicit operator PipelineDefinition(List stages)
{
return Create(stages);
}
}
///
/// A pipeline composed of instances of .
///
/// The type of the input.
/// The type of the output.
public sealed class BsonDocumentStagePipelineDefinition : PipelineDefinition
{
private readonly IBsonSerializer _outputSerializer;
private readonly List _stages;
///
/// Initializes a new instance of the class.
///
/// The stages.
/// The output serializer.
public BsonDocumentStagePipelineDefinition(IEnumerable stages, IBsonSerializer outputSerializer = null)
{
_stages = Ensure.IsNotNull(stages, nameof(stages)).ToList();
_outputSerializer = outputSerializer;
}
///
public override IBsonSerializer OutputSerializer => _outputSerializer;
///
/// Gets the stages.
///
public IList Documents
{
get { return _stages; }
}
///
public override IEnumerable Stages => _stages.Select(s => new BsonDocumentPipelineStageDefinition(s, _outputSerializer));
///
public override RenderedPipelineDefinition Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry)
{
return new RenderedPipelineDefinition(
_stages,
_outputSerializer ?? (inputSerializer as IBsonSerializer) ?? serializerRegistry.GetSerializer());
}
}
///
/// A pipeline composed of instances of .
///
/// The type of the input.
/// The type of the output.
public sealed class PipelineStagePipelineDefinition : PipelineDefinition
{
private readonly IList _stages;
private readonly IBsonSerializer _outputSerializer;
///
/// Initializes a new instance of the class.
///
/// The stages.
/// The output serializer.
public PipelineStagePipelineDefinition(IEnumerable stages, IBsonSerializer outputSerializer = null)
{
_stages = VerifyStages(Ensure.IsNotNull(stages, nameof(stages)).ToList());
_outputSerializer = outputSerializer;
}
///
public override IBsonSerializer OutputSerializer => _outputSerializer;
///
/// Gets the serializer.
///
[Obsolete("Use OutputSerializer instead.")]
public IBsonSerializer Serializer
{
get { return _outputSerializer; }
}
///
/// Gets the stages.
///
public override IEnumerable Stages => _stages;
///
public override RenderedPipelineDefinition Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry)
{
var pipeline = new List();
IBsonSerializer currentSerializer = inputSerializer;
foreach (var stage in _stages)
{
var renderedStage = stage.Render(currentSerializer, serializerRegistry);
currentSerializer = renderedStage.OutputSerializer;
if (renderedStage.Document.ElementCount > 0)
{
pipeline.Add(renderedStage.Document);
}
}
return new RenderedPipelineDefinition(
pipeline,
_outputSerializer ?? (currentSerializer as IBsonSerializer) ?? serializerRegistry.GetSerializer());
}
private static List VerifyStages(List stages)
{
var nextInputType = typeof(TInput);
for (int i = 0; i < stages.Count; i++)
{
if (stages[i].InputType != nextInputType)
{
var message = string.Format(
"The input type to stage[{0}] was expected to be {1}, but was {2}.",
i,
nextInputType,
stages[i].InputType);
throw new ArgumentException(message, "stages");
}
nextInputType = stages[i].OutputType;
}
if (nextInputType != typeof(TOutput))
{
var message = string.Format(
"The output type to the last stage was expected to be {0}, but was {1}.",
nextInputType,
stages.Last().OutputType);
throw new ArgumentException(message, "stages");
}
return stages;
}
}
internal class OptimizingPipelineDefinition : PipelineDefinition
{
private readonly PipelineDefinition _wrapped;
public OptimizingPipelineDefinition(PipelineDefinition wrapped)
{
_wrapped = wrapped;
}
///
public override IBsonSerializer OutputSerializer => _wrapped.OutputSerializer;
///
public override IEnumerable Stages => _wrapped.Stages;
public override RenderedPipelineDefinition Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry)
{
var rendered = _wrapped.Render(inputSerializer, serializerRegistry);
// do some combining of $match documents if possible. This is optimized for the
// OfType case where we've added a discriminator as a match at the beginning of the pipeline.
if (rendered.Documents.Count > 1)
{
var firstStage = rendered.Documents[0].GetElement(0);
var secondStage = rendered.Documents[1].GetElement(0);
if (firstStage.Name == "$match" && secondStage.Name == "$match")
{
var combinedFilter = Builders.Filter.And(
(BsonDocument)firstStage.Value,
(BsonDocument)secondStage.Value);
var combinedStage = new BsonDocument("$match", combinedFilter.Render(BsonDocumentSerializer.Instance, serializerRegistry));
rendered.Documents[0] = combinedStage;
rendered.Documents.RemoveAt(1);
}
}
return rendered;
}
}
}