/* 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; } } }