Skip to content

Commit 5975587

Browse files
committed
CSHARP-2215: Only allow scalar values for array fields where appropriate.
1 parent ee541a0 commit 5975587

File tree

3 files changed

+76
-33
lines changed

3 files changed

+76
-33
lines changed

src/MongoDB.Driver/FieldDefinition.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,26 @@ public static implicit operator FieldDefinition<TDocument>(string fieldName)
172172
public abstract class FieldDefinition<TDocument, TField>
173173
{
174174
/// <summary>
175-
/// Renders the field to a <see cref="String"/>.
175+
/// Renders the field to a <see cref="RenderedFieldDefinition{TField}"/>.
176176
/// </summary>
177177
/// <param name="documentSerializer">The document serializer.</param>
178178
/// <param name="serializerRegistry">The serializer registry.</param>
179-
/// <returns>A <see cref="String"/>.</returns>
179+
/// <returns>A <see cref="RenderedFieldDefinition{TField}"/>.</returns>
180180
public abstract RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry);
181181

182+
/// Renders the field to a <see cref="RenderedFieldDefinition{TField}"/>.
183+
/// <param name="documentSerializer"></param>
184+
/// <param name="serializerRegistry"></param>
185+
/// <param name="allowScalarValueForArrayField"></param>
186+
/// <returns>A <see cref="RenderedFieldDefinition{TField}"/>.</returns>
187+
public virtual RenderedFieldDefinition<TField> Render(
188+
IBsonSerializer<TDocument> documentSerializer,
189+
IBsonSerializerRegistry serializerRegistry,
190+
bool allowScalarValueForArrayField)
191+
{
192+
return Render(documentSerializer, serializerRegistry); // ignore allowScalarValueForArrayField if not overridden by subclass
193+
}
194+
182195
/// <summary>
183196
/// Performs an implicit conversion from <see cref="System.String" /> to <see cref="FieldDefinition{TDocument, TField}" />.
184197
/// </summary>
@@ -292,6 +305,15 @@ public Expression<Func<TDocument, TField>> Expression
292305

293306
/// <inheritdoc />
294307
public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
308+
{
309+
return Render(documentSerializer, serializerRegistry, allowScalarValueForArrayField: false);
310+
}
311+
312+
/// <inheritdoc />
313+
public override RenderedFieldDefinition<TField> Render(
314+
IBsonSerializer<TDocument> documentSerializer,
315+
IBsonSerializerRegistry serializerRegistry,
316+
bool allowScalarValueForArrayField)
295317
{
296318
var lambda = (LambdaExpression)PartialEvaluator.Evaluate(_expression);
297319
var bindingContext = new PipelineBindingContext(serializerRegistry);
@@ -308,7 +330,7 @@ public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument
308330

309331
var underlyingSerializer = field.Serializer;
310332
var fieldSerializer = underlyingSerializer as IBsonSerializer<TField>;
311-
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField));
333+
var valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField);
312334

313335
return new RenderedFieldDefinition<TField>(field.FieldName, fieldSerializer, valueSerializer, underlyingSerializer);
314336
}
@@ -365,6 +387,15 @@ public StringFieldDefinition(string fieldName, IBsonSerializer<TField> fieldSeri
365387

366388
/// <inheritdoc />
367389
public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
390+
{
391+
return Render(documentSerializer, serializerRegistry, allowScalarValueForArrayField: false);
392+
}
393+
394+
/// <inheritdoc />
395+
public override RenderedFieldDefinition<TField> Render(
396+
IBsonSerializer<TDocument> documentSerializer,
397+
IBsonSerializerRegistry serializerRegistry,
398+
bool allowScalarValueForArrayField)
368399
{
369400
string resolvedName;
370401
IBsonSerializer underlyingSerializer;
@@ -379,7 +410,7 @@ public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument
379410
}
380411
else if (underlyingSerializer != null)
381412
{
382-
valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField));
413+
valueSerializer = (IBsonSerializer<TField>)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField), allowScalarValueForArrayField);
383414
}
384415
else
385416
{

src/MongoDB.Driver/FieldValueSerializerHelper.cs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,11 @@ internal static class FieldValueSerializerHelper
2828
{
2929
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType)
3030
{
31-
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, 0);
31+
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false);
3232
}
3333

34-
private static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, int recursionLevel)
34+
public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, bool allowScalarValueForArrayField)
3535
{
36-
if (recursionLevel > 1)
37-
{
38-
throw new ArgumentException("Unexpectedly high recursion level.", nameof(recursionLevel));
39-
}
40-
4136
var fieldType = fieldSerializer.ValueType;
4237

4338
// these will normally be equal unless we've removed some Convert(s) that the compiler put in
@@ -121,17 +116,17 @@ private static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSe
121116
return (IBsonSerializer)ienumerableSerializerConstructor.Invoke(new object[] { itemSerializer });
122117
}
123118

124-
// if the fieldSerializer is an array serializer try to adapt its itemSerializer for valueType
125-
IBsonArraySerializer arraySerializer;
126-
if ((arraySerializer = fieldSerializer as IBsonArraySerializer) != null)
119+
if (allowScalarValueForArrayField)
127120
{
128-
BsonSerializationInfo itemSerializationInfo;
129-
if (arraySerializer.TryGetItemSerializationInfo(out itemSerializationInfo))
121+
// if the fieldSerializer is an array serializer try to adapt its itemSerializer for valueType
122+
IBsonArraySerializer arraySerializer;
123+
if ((arraySerializer = fieldSerializer as IBsonArraySerializer) != null)
130124
{
131-
if (recursionLevel == 0)
125+
BsonSerializationInfo itemSerializationInfo;
126+
if (arraySerializer.TryGetItemSerializationInfo(out itemSerializationInfo))
132127
{
133128
var itemSerializer = itemSerializationInfo.Serializer;
134-
return GetSerializerForValueType(itemSerializer, serializerRegistry, valueType, recursionLevel + 1);
129+
return GetSerializerForValueType(itemSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false);
135130
}
136131
}
137132
}
@@ -150,7 +145,7 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer
150145
}
151146
else
152147
{
153-
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType);
148+
return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false);
154149
}
155150
}
156151

src/MongoDB.Driver/FilterDefinitionBuilder.cs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ public FilterDefinition<TDocument> ElemMatch<TItem>(Expression<Func<TDocument, I
423423
/// <returns>An equality filter.</returns>
424424
public FilterDefinition<TDocument> Eq<TField>(FieldDefinition<TDocument, TField> field, TField value)
425425
{
426-
return new SimpleFilterDefinition<TDocument, TField>(field, value);
426+
return new SimpleFilterDefinition<TDocument, TField>(field, value, allowScalarValueForArrayField: true);
427427
}
428428

429429
/// <summary>
@@ -661,7 +661,7 @@ public FilterDefinition<TDocument> Gt(FieldDefinition<TDocument, ulong> field, u
661661
/// <returns>A greater than filter.</returns>
662662
public FilterDefinition<TDocument> Gt<TField>(FieldDefinition<TDocument, TField> field, TField value)
663663
{
664-
return new OperatorFilterDefinition<TDocument, TField>("$gt", field, value);
664+
return new OperatorFilterDefinition<TDocument, TField>("$gt", field, value, allowScalarValueForArrayField: true);
665665
}
666666

667667
/// <summary>
@@ -733,7 +733,7 @@ public FilterDefinition<TDocument> Gte(FieldDefinition<TDocument, ulong> field,
733733
/// <returns>A greater than or equal filter.</returns>
734734
public FilterDefinition<TDocument> Gte<TField>(FieldDefinition<TDocument, TField> field, TField value)
735735
{
736-
return new OperatorFilterDefinition<TDocument, TField>("$gte", field, value);
736+
return new OperatorFilterDefinition<TDocument, TField>("$gte", field, value, allowScalarValueForArrayField: true);
737737
}
738738

739739
/// <summary>
@@ -781,7 +781,7 @@ public FilterDefinition<TDocument> Gte<TField>(Expression<Func<TDocument, TField
781781
/// <returns>An in filter.</returns>
782782
public FilterDefinition<TDocument> In<TField>(FieldDefinition<TDocument, TField> field, IEnumerable<TField> values)
783783
{
784-
return new SingleItemAsArrayOperatorFilterDefinition<TDocument, TField>("$in", field, values);
784+
return new SingleItemAsArrayOperatorFilterDefinition<TDocument, TField>("$in", field, values, allowScalarValueForArrayField: true);
785785
}
786786

787787
/// <summary>
@@ -803,7 +803,7 @@ public FilterDefinition<TDocument> In<TField>(Expression<Func<TDocument, TField>
803803
/// <returns>A schema filter.</returns>
804804
public FilterDefinition<TDocument> JsonSchema(BsonDocument schema)
805805
{
806-
return new BsonDocumentFilterDefinition<TDocument>(new BsonDocument("$jsonSchema", schema));
806+
return new BsonDocumentFilterDefinition<TDocument>(new BsonDocument("$jsonSchema", schema));
807807
}
808808

809809
/// <summary>
@@ -839,7 +839,7 @@ public FilterDefinition<TDocument> Lt(FieldDefinition<TDocument, ulong> field, u
839839
/// <returns>A less than filter.</returns>
840840
public FilterDefinition<TDocument> Lt<TField>(FieldDefinition<TDocument, TField> field, TField value)
841841
{
842-
return new OperatorFilterDefinition<TDocument, TField>("$lt", field, value);
842+
return new OperatorFilterDefinition<TDocument, TField>("$lt", field, value, allowScalarValueForArrayField: true);
843843
}
844844

845845
/// <summary>
@@ -911,7 +911,7 @@ public FilterDefinition<TDocument> Lte(FieldDefinition<TDocument, ulong> field,
911911
/// <returns>A less than or equal filter.</returns>
912912
public FilterDefinition<TDocument> Lte<TField>(FieldDefinition<TDocument, TField> field, TField value)
913913
{
914-
return new OperatorFilterDefinition<TDocument, TField>("$lte", field, value);
914+
return new OperatorFilterDefinition<TDocument, TField>("$lte", field, value, allowScalarValueForArrayField: true);
915915
}
916916

917917
/// <summary>
@@ -983,7 +983,7 @@ public FilterDefinition<TDocument> Mod(Expression<Func<TDocument, object>> field
983983
/// <returns>A not equal filter.</returns>
984984
public FilterDefinition<TDocument> Ne<TField>(FieldDefinition<TDocument, TField> field, TField value)
985985
{
986-
return new OperatorFilterDefinition<TDocument, TField>("$ne", field, value);
986+
return new OperatorFilterDefinition<TDocument, TField>("$ne", field, value, allowScalarValueForArrayField: true);
987987
}
988988

989989
/// <summary>
@@ -2019,17 +2019,23 @@ internal sealed class OperatorFilterDefinition<TDocument, TField> : FilterDefini
20192019
private readonly string _operatorName;
20202020
private readonly FieldDefinition<TDocument, TField> _field;
20212021
private readonly TField _value;
2022+
private readonly bool _allowScalarValueForArrayField;
20222023

2023-
public OperatorFilterDefinition(string operatorName, FieldDefinition<TDocument, TField> field, TField value)
2024+
public OperatorFilterDefinition(
2025+
string operatorName,
2026+
FieldDefinition<TDocument, TField> field,
2027+
TField value,
2028+
bool allowScalarValueForArrayField = false)
20242029
{
20252030
_operatorName = Ensure.IsNotNull(operatorName, operatorName);
20262031
_field = Ensure.IsNotNull(field, nameof(field));
20272032
_value = value;
2033+
_allowScalarValueForArrayField = allowScalarValueForArrayField;
20282034
}
20292035

20302036
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
20312037
{
2032-
var renderedField = _field.Render(documentSerializer, serializerRegistry);
2038+
var renderedField = _field.Render(documentSerializer, serializerRegistry, _allowScalarValueForArrayField);
20332039

20342040
var document = new BsonDocument();
20352041
using (var bsonWriter = new BsonDocumentWriter(document))
@@ -2108,16 +2114,21 @@ internal sealed class SimpleFilterDefinition<TDocument, TField> : FilterDefiniti
21082114
{
21092115
private readonly FieldDefinition<TDocument, TField> _field;
21102116
private readonly TField _value;
2117+
private readonly bool _allowScalarValueForArrayField;
21112118

2112-
public SimpleFilterDefinition(FieldDefinition<TDocument, TField> field, TField value)
2119+
public SimpleFilterDefinition(
2120+
FieldDefinition<TDocument, TField> field,
2121+
TField value,
2122+
bool allowScalarValueForArrayField = false)
21132123
{
21142124
_field = Ensure.IsNotNull(field, nameof(field));
21152125
_value = value;
2126+
_allowScalarValueForArrayField = allowScalarValueForArrayField;
21162127
}
21172128

21182129
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
21192130
{
2120-
var renderedField = _field.Render(documentSerializer, serializerRegistry);
2131+
var renderedField = _field.Render(documentSerializer, serializerRegistry, _allowScalarValueForArrayField);
21212132

21222133
var document = new BsonDocument();
21232134
using (var bsonWriter = new BsonDocumentWriter(document))
@@ -2138,17 +2149,23 @@ internal sealed class SingleItemAsArrayOperatorFilterDefinition<TDocument, TFiel
21382149
private readonly string _operatorName;
21392150
private readonly FieldDefinition<TDocument, TField> _field;
21402151
private readonly IEnumerable<TField> _values;
2152+
private readonly bool _allowScalarValueForArrayField;
21412153

2142-
public SingleItemAsArrayOperatorFilterDefinition(string operatorName, FieldDefinition<TDocument, TField> field, IEnumerable<TField> values)
2154+
public SingleItemAsArrayOperatorFilterDefinition(
2155+
string operatorName,
2156+
FieldDefinition<TDocument, TField> field,
2157+
IEnumerable<TField> values,
2158+
bool allowScalarValueForArrayField = false)
21432159
{
21442160
_operatorName = Ensure.IsNotNull(operatorName, operatorName);
21452161
_field = Ensure.IsNotNull(field, nameof(field));
21462162
_values = Ensure.IsNotNull(values, nameof(values));
2163+
_allowScalarValueForArrayField = allowScalarValueForArrayField;
21472164
}
21482165

21492166
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
21502167
{
2151-
var renderedField = _field.Render(documentSerializer, serializerRegistry);
2168+
var renderedField = _field.Render(documentSerializer, serializerRegistry, _allowScalarValueForArrayField);
21522169

21532170
var document = new BsonDocument();
21542171
using (var bsonWriter = new BsonDocumentWriter(document))

0 commit comments

Comments
 (0)