[CSHARP-4140] Only use $getField when absolutely necessary Created: 15/Apr/22  Updated: 28/Oct/23  Resolved: 19/Apr/22

Status: Closed
Project: C# Driver
Component/s: LINQ3
Affects Version/s: 2.15.0
Fix Version/s: 2.15.1

Type: Bug Priority: Unknown
Reporter: Felix König Assignee: Robert Stam
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

The LINQ3 driver is using '$getField' for projections in some circumstances, even though it shouldn't if the server does not support it yet. Here's a small unit test to showcase the problem:

[Test]
public void reproduce_linq_v3_error_for_number_like_projection()
{
    var settings = MongoClientSettings.FromConnectionString("mongodb://localhost:27017/?replicaSet=rs0");
    // using V2 fixes the problem
    settings.LinqProvider = LinqProvider.V3;
    var client = new MongoClient(settings);
    var database = client.GetDatabase("some-database");
    var collection = database.GetCollection<BsonDocument>("some-collection");
 
    // changing this to some non-numerical value like "a" also fixes the problem
    string projectedField = "1";
 
    // Tested against server versions 4.2.19 and 4.4.3, the next line produces the following error:
    // "Command aggregate failed: Invalid $project :: caused by :: Unknown expression $getField"
    // Which makes sense because $getField was introduced in server version 5.0.0
    var result = (from doc in collection.AsQueryable() select doc[projectedField]).ToList();
} 

The full stacktrace looks as follows:

fail: TPP.Core.Commands.CommandProcessor[0]
      An exception occured while executing command 'balance'. User: User(console-admin/admin), Original text: tokens
      MongoDB.Driver.MongoCommandException: Command aggregate failed: Invalid $project :: caused by :: Unknown expression $getField.
         at MongoDB.Driver.Core.WireProtocol.CommandUsingCommandMessageWireProtocol`1.ProcessResponse(ConnectionId connectionId, CommandMessage responseMessage)
         at MongoDB.Driver.Core.WireProtocol.CommandUsingCommandMessageWireProtocol`1.ExecuteAsync(IConnection connection, CancellationToken cancellationToken)
         at MongoDB.Driver.Core.Servers.Server.ServerChannel.ExecuteProtocolAsync[TResult](IWireProtocol`1 protocol, ICoreSession session, CancellationToken cancellationToken)
         at MongoDB.Driver.Core.Operations.RetryableReadOperationExecutor.ExecuteAsync[TResult](IRetryableReadOperation`1 operation, RetryableReadContext context, CancellationToken cancellationToken)
         at MongoDB.Driver.Core.Operations.ReadCommandOperation`1.ExecuteAsync(RetryableReadContext context, CancellationToken cancellationToken)
         at MongoDB.Driver.Core.Operations.AggregateOperation`1.ExecuteAsync(RetryableReadContext context, CancellationToken cancellationToken)
         at MongoDB.Driver.Core.Operations.AggregateOperation`1.ExecuteAsync(IReadBinding binding, CancellationToken cancellationToken)
         at MongoDB.Driver.OperationExecutor.ExecuteReadOperationAsync[TResult](IReadBinding binding, IReadOperation`1 operation, CancellationToken cancellationToken)
         at MongoDB.Driver.MongoCollectionImpl`1.ExecuteReadOperationAsync[TResult](IClientSessionHandle session, IReadOperation`1 operation, ReadPreference readPreference, CancellationToken cancellationToken)
         at MongoDB.Driver.MongoCollectionImpl`1.AggregateAsync[TResult](IClientSessionHandle session, PipelineDefinition`2 pipeline, AggregateOptions options, CancellationToken cancellationToken)
         at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
         at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToExecutableQueryTranslators.ExecutableQuery`3.ExecuteAsync(IClientSessionHandle session, CancellationToken cancellationToken)
         at MongoDB.Driver.IAsyncCursorSourceExtensions.ToListAsync[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)
         at TPP.Persistence.MongoDB.PersistedReservedMoneyCheckers.PinballReservedTokens(User user) in S:\projects\tpp-core\TPP.Persistence.MongoDB\PersistedReservedMoneyCheckers.cs:line 28
         at TPP.Persistence.MongoDB.PersistedReservedMoneyCheckers.AllDatabaseReservedTokens(User user) in S:\projects\tpp-core\TPP.Persistence.MongoDB\PersistedReservedMoneyCheckers.cs:line 22
         at TPP.Persistence.ReserveCheckersBank`1.<>c__DisplayClass3_0.<<GetReservedMoney>b__0>d.MoveNext() in S:\projects\tpp-core\TPP.Persistence\IBank.cs:line 190
      --- End of stack trace from previous location ---
         at TPP.Persistence.ReserveCheckersBank`1.GetReservedMoney(T user) in S:\projects\tpp-core\TPP.Persistence\IBank.cs:line 191
         at TPP.Persistence.ReserveCheckersBank`1.GetAvailableMoney(T user) in S:\projects\tpp-core\TPP.Persistence\IBank.cs:line 197
         at TPP.Core.Commands.Definitions.UserCommands.CheckBalance(CommandContext context) in S:\projects\tpp-core\TPP.Core\Commands\Definitions\UserCommands.cs:line 109
         at TPP.Core.Commands.CommandProcessor.Process(String commandName, IImmutableList`1 args, Message message) in S:\projects\tpp-core\TPP.Core\Commands\CommandProcessor.cs:line 84



 Comments   
Comment by Felix König [ 01/Jun/22 ]

It works now in 2.15.1, thanks!

Comment by Githook User [ 04/May/22 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4140: Skip $getField tests on older servers.
Branch: v2.15.x
https://github.com/mongodb/mongo-csharp-driver/commit/a9767f101858d6488dddcac65f4caa6eb9fd250f

Comment by Githook User [ 04/May/22 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4140: Only use $getField when absolutely necessary.
Branch: v2.15.x
https://github.com/mongodb/mongo-csharp-driver/commit/cd18d884c47d6aae7ba197dfaa5dcf58c7a64d56

Comment by Githook User [ 20/Apr/22 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4140: Skip $getField tests on older servers.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/dbea92c6a70a59cf687d4fdc8677cbd8bd954a0b

Comment by Githook User [ 19/Apr/22 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4140: Only use $getField when absolutely necessary.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/a43d040510ca1e31c67e35da9c4a5aee846de036

Comment by Robert Stam [ 18/Apr/22 ]

We don't actually use the server version when deciding whether to use `$getField` or not. In fact, at the point that decision is made we don't actually know the server version.

Instead, we base the decision on the field name and whether we think it would be valid on its own or as part of a field path.

Looks like we are being too conservative when making that decision. Apparently the server allows anything that doesn't start with a `$` or contain a `.` as a valid field name in `$<fieldname>`.

We will change the logic to be more permissive in what is considered a valid field name that does not require using `$getField`.

Generated at Wed Feb 07 21:47:20 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.