Skip to content

Commit 4173887

Browse files
committed
CSHARP-1950: Allow string or regular expressions in values for $in with string.
1 parent 2cdbaa5 commit 4173887

File tree

10 files changed

+1587
-0
lines changed

10 files changed

+1587
-0
lines changed

src/MongoDB.Bson/Serialization/Serializers/StringSerializer.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ namespace MongoDB.Bson.Serialization.Serializers
2222
/// </summary>
2323
public class StringSerializer : SealedClassSerializerBase<string>, IRepresentationConfigurable<StringSerializer>
2424
{
25+
#region static
26+
private static readonly StringSerializer __instance = new StringSerializer();
27+
28+
// public static properties
29+
/// <summary>
30+
/// Gets a cached instance of a default string serializer.
31+
/// </summary>
32+
public static StringSerializer Instance => __instance;
33+
#endregion
34+
2535
// private fields
2636
private readonly BsonType _representation;
2737

src/MongoDB.Driver/FilterDefinitionBuilder.cs

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using MongoDB.Bson;
2121
using MongoDB.Bson.IO;
2222
using MongoDB.Bson.Serialization;
23+
using MongoDB.Bson.Serialization.Serializers;
2324
using MongoDB.Driver.Core.Misc;
2425
using MongoDB.Driver.GeoJsonObjectModel;
2526
using MongoDB.Driver.Linq;
@@ -291,6 +292,94 @@ public FilterDefinition<TDocument> AnyNin<TItem>(Expression<Func<TDocument, IEnu
291292
return AnyNin(new ExpressionFieldDefinition<TDocument>(field), values);
292293
}
293294

295+
/// <summary>
296+
/// Creates an in filter for a string array field.
297+
/// </summary>
298+
/// <param name="field">The field.</param>
299+
/// <param name="values">The values.</param>
300+
/// <returns>An in filter.</returns>
301+
public FilterDefinition<TDocument> AnyStringIn(FieldDefinition<TDocument, IEnumerable<string>> field, IEnumerable<StringOrRegularExpression> values)
302+
{
303+
return new StringArrayFieldInOrNinFilterDefinition<TDocument>(field, "$in", values);
304+
}
305+
306+
/// <summary>
307+
/// Creates an in filter for a string array field.
308+
/// </summary>
309+
/// <param name="field">The field.</param>
310+
/// <param name="values">The values.</param>
311+
/// <returns>An in filter.</returns>
312+
public FilterDefinition<TDocument> AnyStringIn(FieldDefinition<TDocument, IEnumerable<string>> field, params StringOrRegularExpression[] values)
313+
{
314+
return AnyStringIn(field, (IEnumerable<StringOrRegularExpression>)values);
315+
}
316+
317+
/// <summary>
318+
/// Creates an in filter for a string array field.
319+
/// </summary>
320+
/// <param name="field">The field.</param>
321+
/// <param name="values">The values.</param>
322+
/// <returns>An in filter.</returns>
323+
public FilterDefinition<TDocument> AnyStringIn(Expression<Func<TDocument, IEnumerable<string>>> field, IEnumerable<StringOrRegularExpression> values)
324+
{
325+
return new StringArrayFieldInOrNinFilterDefinition<TDocument>(new ExpressionFieldDefinition<TDocument, IEnumerable<string>>(field), "$in", values);
326+
}
327+
328+
/// <summary>
329+
/// Creates an in filter for a string array field.
330+
/// </summary>
331+
/// <param name="field">The field.</param>
332+
/// <param name="values">The values.</param>
333+
/// <returns>An in filter.</returns>
334+
public FilterDefinition<TDocument> AnyStringIn(Expression<Func<TDocument, IEnumerable<string>>> field, params StringOrRegularExpression[] values)
335+
{
336+
return AnyStringIn(field, (IEnumerable<StringOrRegularExpression>)values);
337+
}
338+
339+
/// <summary>
340+
/// Creates a not in filter for a string array field.
341+
/// </summary>
342+
/// <param name="field">The field.</param>
343+
/// <param name="values">The values.</param>
344+
/// <returns>A not in filter.</returns>
345+
public FilterDefinition<TDocument> AnyStringNin(FieldDefinition<TDocument, IEnumerable<string>> field, IEnumerable<StringOrRegularExpression> values)
346+
{
347+
return new StringArrayFieldInOrNinFilterDefinition<TDocument>(field, "$nin", values);
348+
}
349+
350+
/// <summary>
351+
/// Creates a not in filter for a string array field.
352+
/// </summary>
353+
/// <param name="field">The field.</param>
354+
/// <param name="values">The values.</param>
355+
/// <returns>A not in filter.</returns>
356+
public FilterDefinition<TDocument> AnyStringNin(FieldDefinition<TDocument, IEnumerable<string>> field, params StringOrRegularExpression[] values)
357+
{
358+
return AnyStringNin(field, (IEnumerable<StringOrRegularExpression>)values);
359+
}
360+
361+
/// <summary>
362+
/// Creates a not in filter for a string array field.
363+
/// </summary>
364+
/// <param name="field">The field.</param>
365+
/// <param name="values">The values.</param>
366+
/// <returns>A not in filter.</returns>
367+
public FilterDefinition<TDocument> AnyStringNin(Expression<Func<TDocument, IEnumerable<string>>> field, IEnumerable<StringOrRegularExpression> values)
368+
{
369+
return new StringArrayFieldInOrNinFilterDefinition<TDocument>(new ExpressionFieldDefinition<TDocument, IEnumerable<string>>(field), "$nin", values);
370+
}
371+
372+
/// <summary>
373+
/// Creates a not in filter for a string array field.
374+
/// </summary>
375+
/// <param name="field">The field.</param>
376+
/// <param name="values">The values.</param>
377+
/// <returns>A not in filter.</returns>
378+
public FilterDefinition<TDocument> AnyStringNin(Expression<Func<TDocument, IEnumerable<string>>> field, params StringOrRegularExpression[] values)
379+
{
380+
return AnyStringNin(field, (IEnumerable<StringOrRegularExpression>)values);
381+
}
382+
294383
/// <summary>
295384
/// Creates a bits all clear filter.
296385
/// </summary>
@@ -1408,6 +1497,94 @@ public FilterDefinition<TDocument> SizeLte(Expression<Func<TDocument, object>> f
14081497
return SizeLte(new ExpressionFieldDefinition<TDocument>(field), size);
14091498
}
14101499

1500+
/// <summary>
1501+
/// Creates an in filter for a string field.
1502+
/// </summary>
1503+
/// <param name="field">The field.</param>
1504+
/// <param name="values">The values.</param>
1505+
/// <returns>An in filter.</returns>
1506+
public FilterDefinition<TDocument> StringIn(FieldDefinition<TDocument, string> field, IEnumerable<StringOrRegularExpression> values)
1507+
{
1508+
return new StringFieldInOrNinFilterDefinition<TDocument>(field, "$in", values);
1509+
}
1510+
1511+
/// <summary>
1512+
/// Creates an in filter for a string field.
1513+
/// </summary>
1514+
/// <param name="field">The field.</param>
1515+
/// <param name="values">The values.</param>
1516+
/// <returns>An in filter.</returns>
1517+
public FilterDefinition<TDocument> StringIn(FieldDefinition<TDocument, string> field, params StringOrRegularExpression[] values)
1518+
{
1519+
return StringIn(field, (IEnumerable<StringOrRegularExpression>)values);
1520+
}
1521+
1522+
/// <summary>
1523+
/// Creates an in filter for a string field.
1524+
/// </summary>
1525+
/// <param name="field">The field.</param>
1526+
/// <param name="values">The values.</param>
1527+
/// <returns>An in filter.</returns>
1528+
public FilterDefinition<TDocument> StringIn(Expression<Func<TDocument, string>> field, IEnumerable<StringOrRegularExpression> values)
1529+
{
1530+
return new StringFieldInOrNinFilterDefinition<TDocument>(new ExpressionFieldDefinition<TDocument, string>(field), "$in", values);
1531+
}
1532+
1533+
/// <summary>
1534+
/// Creates an in filter for a string field.
1535+
/// </summary>
1536+
/// <param name="field">The field.</param>
1537+
/// <param name="values">The values.</param>
1538+
/// <returns>An in filter.</returns>
1539+
public FilterDefinition<TDocument> StringIn(Expression<Func<TDocument, string>> field, params StringOrRegularExpression[] values)
1540+
{
1541+
return StringIn(field, (IEnumerable<StringOrRegularExpression>)values);
1542+
}
1543+
1544+
/// <summary>
1545+
/// Creates a not in filter for a string field.
1546+
/// </summary>
1547+
/// <param name="field">The field.</param>
1548+
/// <param name="values">The values.</param>
1549+
/// <returns>A not in filter.</returns>
1550+
public FilterDefinition<TDocument> StringNin(FieldDefinition<TDocument, string> field, IEnumerable<StringOrRegularExpression> values)
1551+
{
1552+
return new StringFieldInOrNinFilterDefinition<TDocument>(field, "$nin", values);
1553+
}
1554+
1555+
/// <summary>
1556+
/// Creates a not in filter for a string field.
1557+
/// </summary>
1558+
/// <param name="field">The field.</param>
1559+
/// <param name="values">The values.</param>
1560+
/// <returns>A not in filter.</returns>
1561+
public FilterDefinition<TDocument> StringNin(FieldDefinition<TDocument, string> field, params StringOrRegularExpression[] values)
1562+
{
1563+
return StringNin(field, (IEnumerable<StringOrRegularExpression>)values);
1564+
}
1565+
1566+
/// <summary>
1567+
/// Creates a not in filter for a string field.
1568+
/// </summary>
1569+
/// <param name="field">The field.</param>
1570+
/// <param name="values">The values.</param>
1571+
/// <returns>A not in filter.</returns>
1572+
public FilterDefinition<TDocument> StringNin(Expression<Func<TDocument, string>> field, IEnumerable<StringOrRegularExpression> values)
1573+
{
1574+
return new StringFieldInOrNinFilterDefinition<TDocument>(new ExpressionFieldDefinition<TDocument, string>(field), "$nin", values);
1575+
}
1576+
1577+
/// <summary>
1578+
/// Creates a not in filter for a string field.
1579+
/// </summary>
1580+
/// <param name="field">The field.</param>
1581+
/// <param name="values">The values.</param>
1582+
/// <returns>A not in filter.</returns>
1583+
public FilterDefinition<TDocument> StringNin(Expression<Func<TDocument, string>> field, params StringOrRegularExpression[] values)
1584+
{
1585+
return StringNin(field, (IEnumerable<StringOrRegularExpression>)values);
1586+
}
1587+
14111588
/// <summary>
14121589
/// Creates a text filter.
14131590
/// </summary>
@@ -2327,6 +2504,85 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
23272504
}
23282505
}
23292506

2507+
internal abstract class StringInOrNinFilterDefinition<TDocument, TField> : FilterDefinition<TDocument>
2508+
{
2509+
private readonly FieldDefinition<TDocument, TField> _field;
2510+
private readonly string _operator;
2511+
private readonly IEnumerable<StringOrRegularExpression> _values;
2512+
2513+
public StringInOrNinFilterDefinition(
2514+
FieldDefinition<TDocument, TField> field,
2515+
string @operator,
2516+
IEnumerable<StringOrRegularExpression> values)
2517+
{
2518+
_field = Ensure.IsNotNull(field, nameof(field));
2519+
_operator = Ensure.IsNotNull(@operator, nameof(@operator));
2520+
_values = Ensure.IsNotNull(values, nameof(values));
2521+
2522+
if (@operator != "$in" && @operator != "$nin")
2523+
{
2524+
throw new ArgumentException($"Invalid operator: {@operator}.");
2525+
}
2526+
}
2527+
2528+
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
2529+
{
2530+
var renderedField = _field.Render(documentSerializer, serializerRegistry, linqProvider);
2531+
2532+
var document = new BsonDocument();
2533+
using (var bsonWriter = new BsonDocumentWriter(document))
2534+
{
2535+
var context = BsonSerializationContext.CreateRoot(bsonWriter);
2536+
var stringSerializer = BsonStringSerializer.Instance;
2537+
var regularExpressionSerializer = BsonRegularExpressionSerializer.Instance;
2538+
2539+
bsonWriter.WriteStartDocument();
2540+
bsonWriter.WriteName(renderedField.FieldName);
2541+
bsonWriter.WriteStartDocument();
2542+
bsonWriter.WriteName(_operator);
2543+
bsonWriter.WriteStartArray();
2544+
foreach (var value in _values)
2545+
{
2546+
if (value?.Type == typeof(BsonRegularExpression))
2547+
{
2548+
regularExpressionSerializer.Serialize(context, value.RegularExpression);
2549+
}
2550+
else
2551+
{
2552+
stringSerializer.Serialize(context, value?.String);
2553+
}
2554+
}
2555+
bsonWriter.WriteEndArray();
2556+
bsonWriter.WriteEndDocument();
2557+
bsonWriter.WriteEndDocument();
2558+
}
2559+
2560+
return document;
2561+
}
2562+
}
2563+
2564+
internal sealed class StringArrayFieldInOrNinFilterDefinition<TDocument> : StringInOrNinFilterDefinition<TDocument, IEnumerable<string>>
2565+
{
2566+
public StringArrayFieldInOrNinFilterDefinition(
2567+
FieldDefinition<TDocument, IEnumerable<string>> field,
2568+
string @operator,
2569+
IEnumerable<StringOrRegularExpression> values)
2570+
: base(field, @operator, values)
2571+
{
2572+
}
2573+
}
2574+
2575+
internal sealed class StringFieldInOrNinFilterDefinition<TDocument> : StringInOrNinFilterDefinition<TDocument, string>
2576+
{
2577+
public StringFieldInOrNinFilterDefinition(
2578+
FieldDefinition<TDocument, string> field,
2579+
string @operator,
2580+
IEnumerable<StringOrRegularExpression> values)
2581+
: base(field, @operator, values)
2582+
{
2583+
}
2584+
}
2585+
23302586
internal sealed class UInt32GreaterThanFilterDefinition<TDocument> : FilterDefinition<TDocument>
23312587
{
23322588
private readonly string _operatorName;

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public static AstFieldOperationFilter Ne(AstFilterField field, BsonValue value)
135135
return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Ne, value));
136136
}
137137

138+
public static AstFieldOperationFilter Nin(AstFilterField field, IEnumerable<BsonValue> values)
139+
{
140+
return new AstFieldOperationFilter(field, new AstNinFilterOperation(values));
141+
}
142+
138143
public static AstFilter Not(AstFilter filter)
139144
{
140145
if (filter is AstFieldOperationFilter fieldOperationFilter)

src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Collections.Generic;
1718
using System.Globalization;
1819
using System.Reflection;
1920
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
@@ -23,6 +24,10 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2324
internal static class StringMethod
2425
{
2526
// private static fields
27+
private static readonly MethodInfo __anyStringInWithEnumerable;
28+
private static readonly MethodInfo __anyStringInWithParams;
29+
private static readonly MethodInfo __anyStringNinWithEnumerable;
30+
private static readonly MethodInfo __anyStringNinWithParams;
2631
private static readonly MethodInfo __containsWithChar;
2732
private static readonly MethodInfo __containsWithCharAndComparisonType;
2833
private static readonly MethodInfo __containsWithString;
@@ -58,6 +63,10 @@ internal static class StringMethod
5863
private static readonly MethodInfo __startsWithWithString;
5964
private static readonly MethodInfo __startsWithWithStringAndComparisonType;
6065
private static readonly MethodInfo __startsWithWithStringAndIgnoreCaseAndCulture;
66+
private static readonly MethodInfo __stringInWithEnumerable;
67+
private static readonly MethodInfo __stringInWithParams;
68+
private static readonly MethodInfo __stringNinWithEnumerable;
69+
private static readonly MethodInfo __stringNinWithParams;
6170
private static readonly MethodInfo __strLenBytes;
6271
private static readonly MethodInfo __substrBytes;
6372
private static readonly MethodInfo __substring;
@@ -90,6 +99,10 @@ static StringMethod()
9099
__startsWithWithChar = null;
91100
#endif
92101

102+
__anyStringInWithEnumerable = ReflectionInfo.Method((IEnumerable<string> s, IEnumerable<StringOrRegularExpression> values) => s.AnyStringIn(values));
103+
__anyStringInWithParams = ReflectionInfo.Method((IEnumerable<string> s, StringOrRegularExpression[] values) => s.AnyStringIn(values));
104+
__anyStringNinWithEnumerable = ReflectionInfo.Method((IEnumerable<string> s, IEnumerable<StringOrRegularExpression> values) => s.AnyStringNin(values));
105+
__anyStringNinWithParams = ReflectionInfo.Method((IEnumerable<string> s, StringOrRegularExpression[] values) => s.AnyStringNin(values));
93106
__containsWithString = ReflectionInfo.Method((string s, string value) => s.Contains(value));
94107
__endsWithWithString = ReflectionInfo.Method((string s, string value) => s.EndsWith(value));
95108
__endsWithWithStringAndComparisonType = ReflectionInfo.Method((string s, string value, StringComparison comparisonType) => s.EndsWith(value, comparisonType));
@@ -120,6 +133,10 @@ static StringMethod()
120133
__startsWithWithString = ReflectionInfo.Method((string s, string value) => s.StartsWith(value));
121134
__startsWithWithStringAndComparisonType = ReflectionInfo.Method((string s, string value, StringComparison comparisonType) => s.StartsWith(value, comparisonType));
122135
__startsWithWithStringAndIgnoreCaseAndCulture = ReflectionInfo.Method((string s, string value, bool ignoreCase, CultureInfo culture) => s.StartsWith(value, ignoreCase, culture));
136+
__stringInWithEnumerable = ReflectionInfo.Method((string s, IEnumerable<StringOrRegularExpression> values) => s.StringIn(values));
137+
__stringInWithParams = ReflectionInfo.Method((string s, StringOrRegularExpression[] values) => s.StringIn(values));
138+
__stringNinWithEnumerable = ReflectionInfo.Method((string s, IEnumerable<StringOrRegularExpression> values) => s.StringNin(values));
139+
__stringNinWithParams = ReflectionInfo.Method((string s, StringOrRegularExpression[] values) => s.StringNin(values));
123140
__strLenBytes = ReflectionInfo.Method((string s) => s.StrLenBytes());
124141
__substrBytes = ReflectionInfo.Method((string s, int startIndex, int length) => s.SubstrBytes(startIndex, length));
125142
__substring = ReflectionInfo.Method((string s, int startIndex) => s.Substring(startIndex));
@@ -137,6 +154,10 @@ static StringMethod()
137154
}
138155

139156
// public properties
157+
public static MethodInfo AnyStringInWithEnumerable => __anyStringInWithEnumerable;
158+
public static MethodInfo AnyStringInWithParams => __anyStringInWithParams;
159+
public static MethodInfo AnyStringNinWithEnumerable => __anyStringNinWithEnumerable;
160+
public static MethodInfo AnyStringNinWithParams => __anyStringNinWithParams;
140161
public static MethodInfo ContainsWithChar => __containsWithChar;
141162
public static MethodInfo ContainsWithCharAndComparisonType => __containsWithCharAndComparisonType;
142163
public static MethodInfo ContainsWithString => __containsWithString;
@@ -172,6 +193,10 @@ static StringMethod()
172193
public static MethodInfo StartsWithWithString => __startsWithWithString;
173194
public static MethodInfo StartsWithWithStringAndComparisonType => __startsWithWithStringAndComparisonType;
174195
public static MethodInfo StartsWithWithStringAndIgnoreCaseAndCulture => __startsWithWithStringAndIgnoreCaseAndCulture;
196+
public static MethodInfo StringInWithEnumerable => __stringInWithEnumerable;
197+
public static MethodInfo StringInWithParams => __stringInWithParams;
198+
public static MethodInfo StringNinWithEnumerable => __stringNinWithEnumerable;
199+
public static MethodInfo StringNinWithParams => __stringNinWithParams;
175200
public static MethodInfo StrLenBytes => __strLenBytes;
176201
public static MethodInfo SubstrBytes => __substrBytes;
177202
public static MethodInfo Substring => __substring;

0 commit comments

Comments
 (0)