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