/* 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 System.Linq.Expressions; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver { /// /// Extension methods for projections. /// public static class ProjectionDefinitionExtensions { /// /// Combines an existing projection with a projection that filters the contents of an array. /// /// The type of the document. /// The type of the item. /// The projection. /// The field. /// The filter. /// /// A combined projection. /// public static ProjectionDefinition ElemMatch(this ProjectionDefinition projection, FieldDefinition field, FilterDefinition filter) { var builder = Builders.Projection; return builder.Combine(projection, builder.ElemMatch(field, filter)); } /// /// Combines an existing projection with a projection that filters the contents of an array. /// /// The type of the document. /// The type of the item. /// The projection. /// The field. /// The filter. /// /// A combined projection. /// public static ProjectionDefinition ElemMatch(this ProjectionDefinition projection, Expression>> field, FilterDefinition filter) { var builder = Builders.Projection; return builder.Combine(projection, builder.ElemMatch(field, filter)); } /// /// Combines an existing projection with a projection that filters the contents of an array. /// /// The type of the document. /// The type of the item. /// The projection. /// The field. /// The filter. /// /// A combined projection. /// public static ProjectionDefinition ElemMatch(this ProjectionDefinition projection, Expression>> field, Expression> filter) { var builder = Builders.Projection; return builder.Combine(projection, builder.ElemMatch(field, filter)); } /// /// Combines an existing projection with a projection that excludes a field. /// /// The type of the document. /// The projection. /// The field. /// /// A combined projection. /// public static ProjectionDefinition Exclude(this ProjectionDefinition projection, FieldDefinition field) { var builder = Builders.Projection; return builder.Combine(projection, builder.Exclude(field)); } /// /// Combines an existing projection with a projection that excludes a field. /// /// The type of the document. /// The projection. /// The field. /// /// A combined projection. /// public static ProjectionDefinition Exclude(this ProjectionDefinition projection, Expression> field) { var builder = Builders.Projection; return builder.Combine(projection, builder.Exclude(field)); } /// /// Combines an existing projection with a projection that includes a field. /// /// The type of the document. /// The projection. /// The field. /// /// A combined projection. /// public static ProjectionDefinition Include(this ProjectionDefinition projection, FieldDefinition field) { var builder = Builders.Projection; return builder.Combine(projection, builder.Include(field)); } /// /// Combines an existing projection with a projection that includes a field. /// /// The type of the document. /// The projection. /// The field. /// /// A combined projection. /// public static ProjectionDefinition Include(this ProjectionDefinition projection, Expression> field) { var builder = Builders.Projection; return builder.Combine(projection, builder.Include(field)); } /// /// Combines an existing projection with a text score projection. /// /// The type of the document. /// The projection. /// The field. /// /// A combined projection. /// public static ProjectionDefinition MetaTextScore(this ProjectionDefinition projection, string field) { var builder = Builders.Projection; return builder.Combine(projection, builder.MetaTextScore(field)); } /// /// Combines an existing projection with an array slice projection. /// /// The type of the document. /// The projection. /// The field. /// The skip. /// The limit. /// /// A combined projection. /// public static ProjectionDefinition Slice(this ProjectionDefinition projection, FieldDefinition field, int skip, int? limit = null) { var builder = Builders.Projection; return builder.Combine(projection, builder.Slice(field, skip, limit)); } /// /// Combines an existing projection with an array slice projection. /// /// The type of the document. /// The projection. /// The field. /// The skip. /// The limit. /// /// A combined projection. /// public static ProjectionDefinition Slice(this ProjectionDefinition projection, Expression> field, int skip, int? limit = null) { var builder = Builders.Projection; return builder.Combine(projection, builder.Slice(field, skip, limit)); } } /// /// A builder for a projection. /// /// The type of the source. public sealed class ProjectionDefinitionBuilder { /// /// Creates a client side projection that is implemented solely by using a different serializer. /// /// The type of the projection. /// The projection serializer. /// A client side deserialization projection. public ProjectionDefinition As(IBsonSerializer projectionSerializer = null) { return new ClientSideDeserializationProjectionDefinition(projectionSerializer); } /// /// Combines the specified projections. /// /// The projections. /// /// A combined projection. /// public ProjectionDefinition Combine(params ProjectionDefinition[] projections) { return Combine((IEnumerable>)projections); } /// /// Combines the specified projections. /// /// The projections. /// /// A combined projection. /// public ProjectionDefinition Combine(IEnumerable> projections) { return new CombinedProjectionDefinition(projections); } /// /// Creates a projection that filters the contents of an array. /// /// The type of the item. /// The field. /// The filter. /// /// An array filtering projection. /// public ProjectionDefinition ElemMatch(FieldDefinition field, FilterDefinition filter) { return new ElementMatchProjectionDefinition(field, filter); } /// /// Creates a projection that filters the contents of an array. /// /// The type of the item. /// The field. /// The filter. /// /// An array filtering projection. /// public ProjectionDefinition ElemMatch(Expression>> field, FilterDefinition filter) { return ElemMatch(new ExpressionFieldDefinition(field), filter); } /// /// Creates a projection that filters the contents of an array. /// /// The type of the item. /// The field. /// The filter. /// /// An array filtering projection. /// public ProjectionDefinition ElemMatch(Expression>> field, Expression> filter) { return ElemMatch(new ExpressionFieldDefinition(field), new ExpressionFilterDefinition(filter)); } /// /// Creates a projection that excludes a field. /// /// The field. /// /// An exclusion projection. /// public ProjectionDefinition Exclude(FieldDefinition field) { return new SingleFieldProjectionDefinition(field, 0); } /// /// Creates a projection that excludes a field. /// /// The field. /// /// An exclusion projection. /// public ProjectionDefinition Exclude(Expression> field) { return Exclude(new ExpressionFieldDefinition(field)); } /// /// Creates a projection based on the expression. /// /// The type of the result. /// The expression. /// /// An expression projection. /// public ProjectionDefinition Expression(Expression> expression) { return new FindExpressionProjectionDefinition(expression); } /// /// Creates a projection that includes a field. /// /// The field. /// /// An inclusion projection. /// public ProjectionDefinition Include(FieldDefinition field) { return new SingleFieldProjectionDefinition(field, 1); } /// /// Creates a projection that includes a field. /// /// The field. /// /// An inclusion projection. /// public ProjectionDefinition Include(Expression> field) { return Include(new ExpressionFieldDefinition(field)); } /// /// Creates a text score projection. /// /// The field. /// /// A text score projection. /// public ProjectionDefinition MetaTextScore(string field) { return new SingleFieldProjectionDefinition(field, new BsonDocument("$meta", "textScore")); } /// /// Creates an array slice projection. /// /// The field. /// The skip. /// The limit. /// /// An array slice projection. /// public ProjectionDefinition Slice(FieldDefinition field, int skip, int? limit = null) { var value = limit.HasValue ? (BsonValue)new BsonArray { skip, limit.Value } : skip; return new SingleFieldProjectionDefinition(field, new BsonDocument("$slice", value)); } /// /// Creates an array slice projection. /// /// The field. /// The skip. /// The limit. /// /// An array slice projection. /// public ProjectionDefinition Slice(Expression> field, int skip, int? limit = null) { return Slice(new ExpressionFieldDefinition(field), skip, limit); } } internal sealed class CombinedProjectionDefinition : ProjectionDefinition { private readonly List> _projections; public CombinedProjectionDefinition(IEnumerable> projections) { _projections = Ensure.IsNotNull(projections, nameof(projections)).ToList(); } public override BsonDocument Render(IBsonSerializer sourceSerializer, IBsonSerializerRegistry serializerRegistry) { var document = new BsonDocument(); foreach (var projection in _projections) { var renderedProjection = projection.Render(sourceSerializer, serializerRegistry); foreach (var element in renderedProjection.Elements) { // last one wins document.Remove(element.Name); document.Add(element); } } return document; } } internal sealed class ElementMatchProjectionDefinition : ProjectionDefinition { private readonly FieldDefinition _field; private readonly FilterDefinition _filter; public ElementMatchProjectionDefinition(FieldDefinition field, FilterDefinition filter) { _field = Ensure.IsNotNull(field, nameof(field)); _filter = filter; } public override BsonDocument Render(IBsonSerializer sourceSerializer, IBsonSerializerRegistry serializerRegistry) { var renderedField = _field.Render(sourceSerializer, serializerRegistry); IBsonSerializer itemSerializer; if (renderedField.FieldSerializer != null) { var arraySerializer = renderedField.FieldSerializer as IBsonArraySerializer; BsonSerializationInfo itemSerializationInfo; if (arraySerializer == null || !arraySerializer.TryGetItemSerializationInfo(out itemSerializationInfo)) { var message = string.Format("The serializer for field '{0}' must implement IBsonArraySerializer and provide item serialization info.", renderedField.FieldName); throw new InvalidOperationException(message); } itemSerializer = (IBsonSerializer)itemSerializationInfo.Serializer; } else { itemSerializer = serializerRegistry.GetSerializer(); } var renderedFilter = _filter.Render(itemSerializer, serializerRegistry); return new BsonDocument(renderedField.FieldName, new BsonDocument("$elemMatch", renderedFilter)); } } internal sealed class PositionalOperatorProjectionDefinition : ProjectionDefinition { private readonly FieldDefinition _field; public PositionalOperatorProjectionDefinition(FieldDefinition field) { _field = Ensure.IsNotNull(field, nameof(field)); } public override BsonDocument Render(IBsonSerializer sourceSerializer, IBsonSerializerRegistry serializerRegistry) { var renderedField = _field.Render(sourceSerializer, serializerRegistry); return new BsonDocument(renderedField.FieldName + ".$", 1); } } internal sealed class SingleFieldProjectionDefinition : ProjectionDefinition { private readonly FieldDefinition _field; private readonly BsonValue _value; public SingleFieldProjectionDefinition(FieldDefinition field, BsonValue value) { _field = Ensure.IsNotNull(field, nameof(field)); _value = Ensure.IsNotNull(value, nameof(value)); } public override BsonDocument Render(IBsonSerializer sourceSerializer, IBsonSerializerRegistry serializerRegistry) { var renderedField = _field.Render(sourceSerializer, serializerRegistry); return new BsonDocument(renderedField.FieldName, _value); } } }