mongodbmongodb-queryspring-dataspring-data-mongodb

Which limitations of the org.bson.Document do not allow multiple $or / $and expressions? Can we not fix that?


org.springframework.data.mongodb.InvalidMongoDbApiUsageException: 
Due to limitations of the org.bson.Document, you can't add a second '$or' 
expression specified as '$or: [Document{{active=false}}, 
Document{{active=null}}]'; Criteria already contains '$or: 
[Document{{payerId=Document{{$in=[64aeae270d0313320626dc55]}}}}, 
Document{{payerLoginId=Document{{$in=[64aeae270d0313320626dc55]}}}}]'

For the famous above issue there is a well-known workaround, instead of:

criteria.orOperator(Criteria.where(field).is(value), 
                    Criteria.where(field).is(anotherValue));
criteria.orOperator(Criteria.where(field2).is(value2), 
                    Criteria.where(field2).is(anotherValue2));

To chain the multiple $or within an $and:

criteria.andOperator(
new Criteria().orOperator(Criteria.where(field).is(value), 
                          Criteria.where(field).is(anotherValue)),
new Criteria().orOperator(Criteria.where(field2).is(value2), 
                          Criteria.where(field2).is(anotherValue2)));

Though my question is what limitation are we workarounding here? The mongodb itself can work well with:

{$or:[{field:"value"}, {field:"anotherValue"}], 
 $or:[{field2:"value2"}, {field2:"anotherValue2"}]}

UPDATE: Thanks aneroid for pointing this out: multiple $orare not accepted by mongodb query syntax. Now my question would be towards MongoDB's team. Why $and is implicitly assumed when chaining multiple equality conditions, but not when chaining such $orones?


Solution

  • The reason a workaround is necessary is you are not building a BSON query directly. The example code there builds a Java object, which will then be converted to BSON when submitted to the database server.

    When you build a query like

    criteria.orOperator(Criteria.where(field).is(value), 
                        Criteria.where(field).is(anotherValue));
    criteria.orOperator(Criteria.where(field2).is(value2), 
                        Criteria.where(field2).is(anotherValue2));
    

    that is essentially the same as the query:

    {
      "$or":[{"field":value},{"field":anotherValue}],
      "$or":[{"field2":value2},{"field2":anotherValue2}]
    }
    

    Note that this is not a valid Java object as it has the same field name, $or twice.

    The BSON spec does not explicitly prohibit duplicate field names in a document, but Java does:

    Object properties are an instance of java.util.Dictionary, which specifies:

    In any one Dictionary object, every key is associated with at most one value.

    So it is not possible to build a Java object with 2 properties associated with different values, but both with the same name.

    The workaround of wrapping the pair of $or clauses in an $and clause effectively moves them to an array where each $or is in a separate object:

    {"$and":[
      {"$or":[{"field":value},{"field":anotherValue}]},
      {"$or":[{"field2":value2},{"field2":anotherValue2}]}
    ]}
    

    This avoids the duplicate property name in a single object, while allowing the intended logic to be applied.