"cannot infer query fields to set"

Hello,

I’ve implemented a (Python) method for adding/updating data on (sports) matches. It relies on bulkWrite() with multiple UpdateOne() calls, each with the upsert flag set to true. When I use $all in my filter, to make sure I’m matching all the submitted players in a given array, I get this:

[{'index': 0,
'code': 54,
'errmsg': "cannot infer query fields to set, path 'players' is matched twice",
'op': SON([('q', {'players': {'$all': ['ipbfxu', 'hyndee']}}), ('u', {'$set': {'test_name': 'test_value'}}), ('multi', False), ('upsert', True)])}]

Here’s the method, condensed to its bare minimum:

def add_or_update_matches_2(matches):
    db = get_db()
    requests = []
    for match in matches:
        request = UpdateOne(
            {"players": {"$all": match["players"]}},
            {"$set": {"test_name": "test_value"}},
            upsert=True
        )
        requests.append(request)
    if len(requests) == 0:
        return None
    result = db.matches.bulk_write(requests)
    return result

If I remove the $all, it works as I want, except that I need to be able to have players in any order. The error doesn’t seem to come from pymongo, so I guess it’s internal to MongoDB. Also, I don’t understand what the error means and what I’m supposed to do instead. Could it be a bug?

Hi @Gustaf_Liljegren! Welcome to the forums.

There are many reasons this might be failing. Can you provide a few sample documents so I can understand what the structure is like? One possibility I can think of is that if players is an array of documents you need to use $elemMatch with $all as explained here.

1 Like

Thanks for helping out,

Using this short test, I was able to reproduce the problem:

db.matches.bulkWrite( [
    { deleteMany : { "filter" : { "players" : { "$all" : [ "Ada", "Karl" ] } } } },
    { insertOne : { "document" : { "players" : [ "Karl", "Ada" ] } } },
    { updateOne :
        {
            "filter" : { "players" : { "$all" : [ "Ada", "Karl" ] } },
            "update" : { "$set" : { "success" : true } },
            "upsert" : true
        }
    }
] )
db.matches.find( { "players" : { "$all" : [ "Ada", "Karl" ] } } )

If I comment out the line containing insertOne, so that the document is created rather than updated, the “cannot infer query fields to set” error occurs. Note that the same filter works when updating an existing document (and with deleteMany), but when inserting a document using updateOne, it fails.

As you can see, this is a string array, not an array of objects, so the $elemMatch syntax shouldn’t be necessary.

Would love to see a explanation for this behavior.

Hi @Gustaf_Liljegren. Thanks for the repro code! The reason this doesn’t work is due to the upsert: true clause in the updateOne operation. You see, when an update operation with upsert: true doesn’t find any matching documents and the update parameter contains operations using update operator expressions (https://docs.mongodb.com/manual/reference/operator/update/#id1) the server first builds a base document using the query parameter itself and then applies the update operations to it.
In your case, the server is unable to look at "players" : {$all: [ "Ada", "Karl"]} and figure out that the base document is a 2-member array which results in this error.

It is possible that update eventually becomes smart enough to handle this but in the meantime you will need to workaround this limitation in your code.

Many thanks @Prashant_Mital. It makes total sense now. I was able to workaround it using $elemMatch as you suggested:

"players" : { "$all" : [ { "$elemMatch" : { "$eq" : player } } for player in match["players"] ] }

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.