Skip to content

CSHARP-5552: Add support for $convert in LINQ #1659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 82 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
b11abcb
Small correction
papafe Mar 13, 2025
e51d733
Improvements
papafe Mar 13, 2025
7484967
Fixed errors
papafe Mar 13, 2025
d604e2a
Small rename
papafe Mar 14, 2025
e46ebc6
Various corrections
papafe Mar 14, 2025
9ff2347
Behaviour correction
papafe Mar 14, 2025
3d55596
Simplified code
papafe Mar 14, 2025
ebe1bf5
Small improvements
papafe Mar 14, 2025
20763ce
Added test
papafe Mar 17, 2025
64b0082
Several improvements
papafe Mar 17, 2025
be829c9
Added int tests
papafe Mar 18, 2025
4007671
Added convertToLong tests
papafe Mar 18, 2025
9b40d91
Small corrections
papafe Mar 18, 2025
9e6c11a
Various fixes
papafe Mar 18, 2025
f1cc942
Small corrections
papafe Mar 18, 2025
6b3fb54
Fixed docs
papafe Mar 18, 2025
f25e15b
Fixed docs
papafe Mar 18, 2025
73fe700
Added byteOrder, removed format where not necessary
papafe Mar 21, 2025
94651f8
Small fix
papafe Mar 21, 2025
82b3f02
Imrpoved tests
papafe Mar 24, 2025
6bfc029
Added tests
papafe Mar 24, 2025
23556b3
Fixed test
papafe Mar 24, 2025
76dd221
Various fixes
papafe Mar 24, 2025
6b6be70
Naming correction
papafe Mar 24, 2025
311832d
Small naming correction
papafe Mar 24, 2025
34ef476
Small corrections
papafe Mar 24, 2025
5f1732f
Fixes according to PR
papafe Mar 25, 2025
b17c354
Moved byteOrder to outer scope
papafe Mar 25, 2025
37ab91e
Changed order
papafe Mar 25, 2025
54395c1
Improved testing
papafe Mar 25, 2025
5e5701e
Rename
papafe Mar 25, 2025
2b16ebb
Corrected name of feature
papafe Mar 26, 2025
40276ed
Small corrections
papafe Mar 26, 2025
16887f9
Added convert options
papafe Mar 26, 2025
c95d8a8
Added remark
papafe Mar 26, 2025
8c4dbf8
Added missing methods
papafe Mar 26, 2025
22691b4
Added missing methods
papafe Mar 26, 2025
cfff9b3
Various corrections
papafe Mar 26, 2025
a341cfe
Various corrections
papafe Mar 26, 2025
16ec0b1
Improvements
papafe Mar 26, 2025
698690f
TestMethod renaming
papafe Mar 26, 2025
10f05e6
Improved methods
papafe Mar 26, 2025
98fd0e1
Other improvements
papafe Mar 26, 2025
021f4f0
Almost finished tests
papafe Mar 26, 2025
d7bca77
Added more tests
papafe Mar 26, 2025
f94891c
Small corrections
papafe Mar 26, 2025
0664eb1
Small fix
papafe Mar 26, 2025
e4c185b
Corrected name
papafe Mar 26, 2025
3af159e
Various fixes according to PR
papafe Mar 26, 2025
23ddc74
Moving to a simple convert method
papafe Apr 1, 2025
6829b66
Small correction
papafe Apr 1, 2025
b237d0d
Test improvement
papafe Apr 1, 2025
555bbd9
Added tests
papafe Apr 1, 2025
fd9626c
Nits correction
papafe Apr 2, 2025
403c02a
Nit corrections
papafe Apr 2, 2025
69cf61b
Imrpoved
papafe Apr 2, 2025
8e8eea5
Small fix
papafe Apr 2, 2025
6e87398
Tried to move stuff to simplifier
papafe Apr 2, 2025
fb5509e
Small fixes
papafe Apr 2, 2025
3ee0f42
Removed extra line
papafe Apr 2, 2025
705f5b3
Added missing copyright
papafe Apr 3, 2025
2a0829b
Restored simplification
papafe Apr 3, 2025
53b886a
Various fixes
papafe Apr 3, 2025
37cb373
added more tests
papafe Apr 3, 2025
9b88903
Small rename
papafe Apr 3, 2025
5fa5850
Reordered tests
papafe Apr 3, 2025
03c3974
Slight improvements
papafe Apr 3, 2025
9aeabb3
Small fix
papafe Apr 4, 2025
07de4e5
Removed constraints
papafe Apr 4, 2025
39904d0
Tried to add new tests
papafe Apr 4, 2025
e4fc2a7
Added generic convert test
papafe Apr 4, 2025
dc2841e
Small fix
papafe Apr 4, 2025
276434d
Small fixes
papafe Apr 7, 2025
23de869
Fixes according to PR
papafe Apr 7, 2025
d844cd2
Corrections
papafe Apr 7, 2025
ec52d7f
Small corrections
papafe Apr 7, 2025
f8a5a66
Added test
papafe Apr 8, 2025
abda353
Removed unused
papafe Apr 8, 2025
e2f2f6a
Restored files
papafe Apr 8, 2025
afa7bdd
Small fixes
papafe Apr 8, 2025
de05dd6
Small corrections + test fix
papafe Apr 10, 2025
3b19233
Small naming correction
papafe Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/MongoDB.Driver/Core/Misc/Feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class Feature
private static readonly Feature __clientBulkWrite = new Feature("ClientBulkWrite", WireVersion.Server80);
private static readonly Feature __clientSideEncryption = new Feature("ClientSideEncryption", WireVersion.Server42);
private static readonly Feature __clusteredIndexes = new Feature("ClusteredIndexes", WireVersion.Server53);
private static readonly Feature __convertOperatorBinDataToFromNumeric = new Feature("ConvertOperatorBinDataToFromNumeric", WireVersion.Server81);
private static readonly Feature __convertOperatorBinDataToFromString= new Feature("ConvertOperatorBinDataToFromString", WireVersion.Server80);
private static readonly Feature __createIndexCommitQuorum = new Feature("CreateIndexCommitQuorum", WireVersion.Server44);
private static readonly Feature __createIndexesUsingInsertOperations = new Feature("CreateIndexesUsingInsertOperations", WireVersion.Zero, WireVersion.Server42);
private static readonly Feature __csfleRangeAlgorithm = new Feature("CsfleRangeAlgorithm", WireVersion.Server62);
Expand Down Expand Up @@ -193,6 +195,16 @@ public class Feature
/// </summary>
public static Feature ClusteredIndexes => __clusteredIndexes;

/// <summary>
/// Gets the conversion of binary data to/from numeric types feature.
/// </summary>
public static Feature ConvertOperatorBinDataToFromNumeric => __convertOperatorBinDataToFromNumeric;

/// <summary>
/// Gets the conversion of binary data to/from string feature.
/// </summary>
public static Feature ConvertOperatorBinDataToFromString => __convertOperatorBinDataToFromString;

/// <summary>
/// Gets the create index commit quorum feature.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* 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 MongoDB.Bson;

namespace MongoDB.Driver.Linq.Linq3Implementation.Ast
{
internal static class AstEnumExtensions
{
public static string Render(this BsonType type)
{
return type switch
{
BsonType.Array => "array",
BsonType.Binary => "binData",
BsonType.Boolean => "bool",
BsonType.DateTime => "date",
BsonType.Decimal128 => "decimal",
BsonType.Document => "object",
BsonType.Double => "double",
BsonType.Int32 => "int",
BsonType.Int64 => "long",
BsonType.JavaScript => "javascript",
BsonType.JavaScriptWithScope => "javascriptWithScope",
BsonType.MaxKey => "maxKey",
BsonType.MinKey => "minKey",
BsonType.Null => "null",
BsonType.ObjectId => "objectId",
BsonType.RegularExpression => "regex",
BsonType.String => "string",
BsonType.Symbol => "symbol",
BsonType.Timestamp => "timestamp",
BsonType.Undefined => "undefined",
_ => throw new ArgumentException($"Unexpected BSON type: {type}.", nameof(type))
};
}

public static string Render(this ByteOrder byteOrder)
{
return byteOrder switch
{
ByteOrder.BigEndian => "big",
ByteOrder.LittleEndian => "little",
_ => throw new ArgumentException($"Unexpected {nameof(ByteOrder)}: {byteOrder}.", nameof(byteOrder))
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,39 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions
{
internal sealed class AstConvertExpression : AstExpression
{
private readonly ByteOrder? _byteOrder;
private readonly string _format;
private readonly AstExpression _input;
private readonly AstExpression _onError;
private readonly AstExpression _onNull;
private readonly BsonBinarySubType? _subType;
private readonly AstExpression _to;

public AstConvertExpression(
AstExpression input,
AstExpression to,
BsonBinarySubType? subType = null,
ByteOrder? byteOrder = null,
string format = null,
AstExpression onError = null,
AstExpression onNull = null)
{
_input = Ensure.IsNotNull(input, nameof(input));
_to = Ensure.IsNotNull(to, nameof(to));
_subType = subType;
_byteOrder = byteOrder;
_format = format;
_onError = onError;
_onNull = onNull;
}

public ByteOrder? ByteOrder => _byteOrder;
public string Format => _format;
public AstExpression Input => _input;
public override AstNodeType NodeType => AstNodeType.ConvertExpression;
public AstExpression OnError => _onError;
public AstExpression OnNull => _onNull;
public BsonBinarySubType? SubType => _subType;
public AstExpression To => _to;

public override AstNode Accept(AstNodeVisitor visitor)
Expand All @@ -56,9 +68,18 @@ public override BsonValue Render()
{ "$convert", new BsonDocument
{
{ "input", _input.Render() },
{ "to", _to.Render() },
{ "to", _to.Render(), _subType == null },
{ "to", () => new BsonDocument
{
{ "type", _to.Render() },
{ "subtype", (int)_subType!.Value},
},
_subType != null
},
{ "onError", () => _onError.Render(), _onError != null },
{ "onNull", () => _onNull.Render(), _onNull != null }
{ "onNull", () => _onNull.Render(), _onNull != null },
{ "format", () => _format, _format != null },
{ "byteOrder", () => _byteOrder!.Value.Render(), _byteOrder != null }
}
}
};
Expand All @@ -75,7 +96,7 @@ public AstConvertExpression Update(
return this;
}

return new AstConvertExpression(input, to, onError, onNull);
return new AstConvertExpression(input, to, _subType, _byteOrder, _format, onError, onNull);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,21 @@ public static AstExpression Constant(BsonValue value)
return new AstConstantExpression(value);
}

public static AstExpression Convert(AstExpression input, AstExpression to, AstExpression onError = null, AstExpression onNull = null)
public static AstExpression Convert(
AstExpression input,
AstExpression to,
BsonBinarySubType? subType = null,
ByteOrder? byteOrder = null,
string format = null,
AstExpression onError = null,
AstExpression onNull = null)
{
Ensure.IsNotNull(input, nameof(input));
Ensure.IsNotNull(to, nameof(to));

if (to is AstConstantExpression toConstantExpression &&
(toConstantExpression.Value as BsonString)?.Value is string toValue &&
toValue != null &&
onError == null &&
onNull == null)
if (onError == null && onNull == null && subType == null && format == null && byteOrder == null &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider formatting with one condition per line to make it easier to read:

if (to is AstConstantExpression toConstantExpression &&
    (toConstantExpression.Value as BsonString)?.Value is { } toValue &&
    subType == null &&                                                 
    byteOrder == null &&                                               
    format == null &&                                                  
    onError == null &&                                                 
    onNull == null)                                                    

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

to is AstConstantExpression toConstantExpression &&
(toConstantExpression.Value as BsonString)?.Value is { } toValue)
{
var unaryOperator = toValue switch
{
Expand All @@ -278,13 +283,14 @@ public static AstExpression Convert(AstExpression input, AstExpression to, AstEx
"string" => AstUnaryOperator.ToString,
_ => (AstUnaryOperator?)null
};

if (unaryOperator.HasValue)
{
return AstExpression.Unary(unaryOperator.Value, input);
}
}

return new AstConvertExpression(input, to, onError, onNull);
return new AstConvertExpression(input, to, subType, byteOrder, format, onError, onNull);
}

public static AstExpression DateAdd(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
internal static class MqlMethod
{
// private static fields
private static readonly MethodInfo __convert;
private static readonly MethodInfo __constantWithRepresentation;
private static readonly MethodInfo __constantWithSerializer;
private static readonly MethodInfo __dateFromString;
Expand All @@ -37,6 +38,7 @@ internal static class MqlMethod
// static constructor
static MqlMethod()
{
__convert = ReflectionInfo.Method((object value, ConvertOptions<object> options) => Mql.Convert(value, options));
__constantWithRepresentation = ReflectionInfo.Method((object value, BsonType representation) => Mql.Constant(value, representation));
__constantWithSerializer = ReflectionInfo.Method((object value, IBsonSerializer<object> serializer) => Mql.Constant(value, serializer));
__dateFromString = ReflectionInfo.Method((string dateStringl) => Mql.DateFromString(dateStringl));
Expand All @@ -50,6 +52,7 @@ static MqlMethod()
}

// public properties
public static MethodInfo Convert => __convert;
public static MethodInfo ConstantWithRepresentation => __constantWithRepresentation;
public static MethodInfo ConstantWithSerializer => __constantWithSerializer;
public static MethodInfo DateFromString => __dateFromString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/

using System.Linq.Expressions;
using MongoDB.Bson.IO;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary diff.

Copy link
Contributor Author

@papafe papafe Apr 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed it while I was looking at the file. It's a very small change, do we really need to open another PR to fix this?
This is valid also for the other small changes unrelated to the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not suggesting we need another PR for this... I'm just saying we shouldn't modify files that have nothing at all to do with this PR.

This file can be "cleaned" up some future day when we are doing some work that actually involves this file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your current thinking on this unnecessary diff? Can it be removed from this PR?

using MongoDB.Bson.Serialization;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public static TranslatedExpression Translate(TranslationContext context, MemberE
{
case "HasValue": return HasValuePropertyToAggregationExpressionTranslator.Translate(context, expression);
case "Value": return ValuePropertyToAggregationExpressionTranslator.Translate(context, expression);
default: break;
}
}

Expand All @@ -66,7 +65,7 @@ public static TranslatedExpression Translate(TranslationContext context, MemberE

if (!DocumentSerializerHelper.AreMembersRepresentedAsFields(containerTranslation.Serializer, out _))
{
if (member is PropertyInfo propertyInfo && propertyInfo.Name == "Length")
if (member is PropertyInfo propertyInfo && propertyInfo.Name == "Length")
{
return LengthPropertyToAggregationExpressionTranslator.Translate(context, expression);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC
case "Contains": return ContainsMethodToAggregationExpressionTranslator.Translate(context, expression);
case "ContainsKey": return ContainsKeyMethodToAggregationExpressionTranslator.Translate(context, expression);
case "ContainsValue": return ContainsValueMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Convert": return ConvertMethodToAggregationExpressionTranslator.Translate(context, expression);
case "CovariancePopulation": return CovariancePopulationMethodToAggregationExpressionTranslator.Translate(context, expression);
case "CovarianceSample": return CovarianceSampleMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Create": return CreateMethodToAggregationExpressionTranslator.Translate(context, expression);
Expand Down
Loading