// Copyright Epic Games, Inc. All Rights Reserved.
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text.Json;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
namespace HordeServer.Utilities
{
///
/// Extension methods for MongoDB
///
public static class MongoExtensions
{
///
/// Maps a constructor for a type into a classmap
///
public static void MapConstructor(this BsonClassMap classMap, Expression> generator, params string[] argumentNames)
{
NewExpression newExpr = (NewExpression)generator.Body;
classMap.MapConstructor(newExpr.Constructor, argumentNames);
}
///
/// Rounds a time value to its BSON equivalent (ie. milliseconds since Unix Epoch).
///
///
///
public static DateTime RoundToBsonDateTime(DateTime time)
{
return BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(BsonUtils.ToMillisecondsSinceEpoch(time));
}
///
/// Renders a filter definition to a document using the default serializer registry
///
public static BsonDocument Render(this FilterDefinition filter)
{
IBsonSerializerRegistry serializerRegistry = BsonSerializer.SerializerRegistry;
IBsonSerializer documentSerializer = serializerRegistry.GetSerializer();
return filter.Render(documentSerializer, serializerRegistry, MongoDB.Driver.Linq.LinqProvider.V2);
}
///
/// Renders a filter definition to a document using the default serializer registry
///
public static BsonValue Render(this UpdateDefinition update)
{
IBsonSerializerRegistry serializerRegistry = BsonSerializer.SerializerRegistry;
IBsonSerializer documentSerializer = serializerRegistry.GetSerializer();
return update.Render(documentSerializer, serializerRegistry, MongoDB.Driver.Linq.LinqProvider.V2);
}
///
/// Executes a query with a particular index hint
///
///
///
///
///
///
///
///
public static async Task FindWithHintAsync(this IMongoCollection collection, FilterDefinition filter, string? indexHint, Func, Task> processAsync)
{
FindOptions? findOptions = null;
if (indexHint != null)
{
findOptions = new FindOptions { Hint = new BsonString(indexHint) };
}
try
{
return await processAsync(collection.Find(filter, findOptions));
}
catch (MongoCommandException ex) when (indexHint != null && ex.Code == 2)
{
return await processAsync(collection.Find(filter));
}
}
///
/// Filters the documents returned from a search
///
/// The query to filter
/// Index of the first document to return
/// Number of documents to return
/// New query
public static IFindFluent Range(this IFindFluent query, int? index, int? count)
{
if (index != null && index.Value != 0)
{
query = query.Skip(index.Value);
}
if (count != null)
{
query = query.Limit(count.Value);
}
return query;
}
///
/// Filters the documents returned from a search
///
/// The query to filter
/// Index of the first document to return
/// Number of documents to return
/// New query
public static IAggregateFluent Range(this IAggregateFluent query, int? index, int? count)
{
if (index != null && index.Value != 0)
{
query = query.Skip(index.Value);
}
if (count != null)
{
query = query.Limit(count.Value);
}
return query;
}
///
/// Attempts to insert a document into a collection, handling the error case that a document with the given key already exists
///
///
/// Collection to insert into
/// The document to insert
/// Cancellation token for the operation
/// True if the document was inserted, false if it already exists
public static async Task InsertOneIgnoreDuplicatesAsync(this IMongoCollection collection, TDocument newDocument, CancellationToken cancellationToken = default)
{
try
{
await collection.InsertOneAsync(newDocument, cancellationToken: cancellationToken);
return true;
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
return false;
}
else
{
throw;
}
}
}
///
/// Attempts to insert a document into a collection, handling the error case that a document with the given key already exists
///
///
/// Collection to insert into
/// The document to insert
/// Cancellation token for the operation
public static async Task InsertManyIgnoreDuplicatesAsync(this IMongoCollection collection, List newDocuments, CancellationToken cancellationToken = default)
{
try
{
if (newDocuments.Count > 0)
{
await collection.InsertManyAsync(newDocuments, new InsertManyOptions { IsOrdered = false }, cancellationToken);
}
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category != ServerErrorCategory.DuplicateKey)
{
throw;
}
}
catch (MongoBulkWriteException ex)
{
if (ex.WriteErrors.Any(x => x.Category != ServerErrorCategory.DuplicateKey))
{
throw;
}
}
}
///
/// Sets a field to a value, or unsets it if the value is null
///
/// The document type
/// Type of the field to set
/// Update builder
/// Expression for the field to set
/// New value to set
/// Update defintiion
public static UpdateDefinition SetOrUnsetNull(this UpdateDefinitionBuilder updateBuilder, Expression> field, TField? value) where TField : struct
{
if (value.HasValue)
{
return updateBuilder.Set(field, value.Value);
}
else
{
return updateBuilder.Unset(new ExpressionFieldDefinition(field));
}
}
///
/// Sets a field to a value, or unsets it if the value is null
///
/// The document type
/// Type of the field to set
/// Update builder
/// Expression for the field to set
/// New value to set
/// Update defintiion
public static UpdateDefinition SetOrUnsetNull(this UpdateDefinition update, Expression> field, TField? value) where TField : struct
{
if (value.HasValue)
{
return update.Set(field, value.Value);
}
else
{
return update.Unset(new ExpressionFieldDefinition(field));
}
}
///
/// Sets a field to a value, or unsets it if the value is null
///
/// The document type
/// Update builder
/// Expression for the field to set
/// New value to set
/// Update defintiion
public static UpdateDefinition SetOrUnsetBool(this UpdateDefinition update, Expression> field, bool value)
{
if (value)
{
return update.Set(field, value);
}
else
{
return update.Unset(new ExpressionFieldDefinition(field));
}
}
///
/// Sets a field to a value, or unsets it if the value is null
///
/// The document type
/// Update builder
/// Expression for the field to set
/// New value to set
/// Update defintiion
public static UpdateDefinition SetOrUnsetBool(this UpdateDefinitionBuilder updateBuilder, Expression> field, bool value)
{
if (value)
{
return updateBuilder.Set(field, value);
}
else
{
return updateBuilder.Unset(new ExpressionFieldDefinition(field));
}
}
///
/// Sets a field to a value, or unsets it if the value is null
///
/// The document type
/// Type of the field to set
/// Update builder
/// Expression for the field to set
/// New value to set
/// Update defintiion
public static UpdateDefinition SetOrUnsetNullRef(this UpdateDefinitionBuilder updateBuilder, Expression> field, TField? value) where TField : class
{
if (value != null)
{
return updateBuilder.Set(field, value);
}
else
{
return updateBuilder.Unset(new ExpressionFieldDefinition(field));
}
}
///
/// Creates a filter definition from a linq expression. This is not generally explicitly castable, so expose it as a FilterDefinitionBuilder method.
///
/// The document type
/// The filter builder
/// Expression to parse
/// New filter definition
#pragma warning disable IDE0060
public static FilterDefinition Expr(this FilterDefinitionBuilder filter, Expression> expression)
{
return expression;
}
#pragma warning restore IDE0060
///
/// Creates an async enumerator from the given query
///
/// Document type
/// Query source
/// Cancellation token for the operation
///
#pragma warning disable IDE1006 // Naming Styles
#pragma warning disable VSTHRD200 // Use Async suffix
public static async IAsyncEnumerable ToAsyncEnumerable(this IAsyncCursorSource source, [EnumeratorCancellation] CancellationToken cancellationToken)
#pragma warning restore VSTHRD200 // Use Async suffix
#pragma warning restore IDE1006 // Naming Styles
{
using (IAsyncCursor cursor = await source.ToCursorAsync(cancellationToken))
{
while (await cursor.MoveNextAsync(cancellationToken))
{
foreach (T value in cursor.Current)
{
yield return value;
cancellationToken.ThrowIfCancellationRequested();
}
}
}
}
///
/// Converts a JsonElement to a BsonValue
///
/// The element to convert
/// Bson value
public static BsonValue ToBsonValue(this JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.True:
return true;
case JsonValueKind.False:
return false;
case JsonValueKind.String:
return element.GetString();
case JsonValueKind.Array:
return new BsonArray(element.EnumerateArray().Select(x => x.ToBsonValue()));
case JsonValueKind.Object:
return new BsonDocument(element.EnumerateObject().Select(x => new BsonElement(x.Name, x.Value.ToBsonValue())));
default:
return BsonNull.Value;
}
}
}
}