Difference between containedBy and containedIn

Hello,

looking in to documentation I can’t really figure out what is the difference between containedBy and containedIn.

let say I have an array of “available” bloom filter bit locations [0, 10, 25, 2464, 4542] and I am doing query against bunch of profiles that has field “bf” that is optional array with two Integers for example bf = [10, 5000]. I tried using following with mentioned result from .explain():

  1. chaining the query on each array field scans the documents, but give accurate result:

     dateQuery.containedIn("bf.0", bloomFilter);
     dateQuery.containedIn("bf.1", bloomFilter);
    

    → executionTimeMillis 13
    → totalDocsExamined 133
    → totalKeysExamined 416
    → nReturned 0

  2. leaving the array as a key itself, it returns documents that has only one Integer in array matching

    `dateQuery.containedIn("bf", bloomFilter);`
    

    → nReturned 1
    → totalDocsExamined 1
    → totalKeysExamined 416
    → executionTimeMillis 15

  3. containedBy seems to ignore the input “bloom filter” array:

    dateQuery.containedBy("bf", bloomFilter);

    → nReturned 40
    → totalKeysExamined 416
    → totalDocsExamined 133
    → executionTimeMillis 4

What is the correct logic behind containedBy?

thank you!

I believe containedIn uses mongodb $in operator ($in — MongoDB Manual) and containedBy uses mongodb $all operator ($all — MongoDB Manual).

printing out .explain gave me a hint:

parsedQuery Optional(["$and": [[“ag”: ["$lte": 30]], [“ag”: ["$gte": 20]], [“da”: ["$gte": 1]], ["_rperm": ["$in": [(), “*”, “esCzEcObN7”]]], [“po”: ["$in": [(), 0]]], ["$nor": [[“bf”: ["$elemMatch": [“0”: ["": ["$in": [0, 1, 2, 3, 4,… long array of available bits…, 3999, 4000]]]]]]]], [“gp”: ["$geoWithin": ["$centerSphere": [[7, 48], 0.003139224611520955]]]]]])

and nor does not use indexes if I remember correctly. What is one thing, but most confusing for me is that it actually returns documents even they have null in the “bf” field… But maybe that is the problem here…

To be honest, I don’t really understand the parsed query… why $nor and such expressions inside that $nor? Definition on MongoDB goes:

$nor performs a logical NOR operation on an array of one or more query expression and selects the documents that fail all the query expressions in the array.

Definition in Parse documentation:

containedBy(key, values) → {Parse.Query}

Adds a constraint to the query that requires a particular key’s value to be contained by the provided list of values. Get objects where all array elements match.

containedIn(key, value) → {Parse.Query}

Adds a constraint to the query that requires a particular key’s value to be contained in the provided list of values.

Could anyone with a fresh look confirm this is intended behaviour?

It is not clear to me what is your question. Could you clarify? It would be also good if you could share your query.

Here is a cloud function for the query:

Parse.Cloud.define("newProfiles", async (request) => {

    const token = request.user.getSessionToken();
    //get requesting user's profile
    const usrProfQuery = new Parse.Query("PrsProfile");
    usrProfQuery.equalTo("objectId", request.user.id);
    const usrProfile = await usrProfQuery.first({ sessionToken: token });
    if (!usrProfile) {
        throw "current user profile not found";
    }    

    //basic query criteria common for all specific queries
    const minAge = request.user.get("minA");
    const maxAge = request.user.get("maxA");
    const ori = usrProfile.get("o");
    const partnerOri = usrProfile.get("po");
    //Gender: 0 - woman, 1 - notDefined, 2 - man
    const usrGender = usrProfile.get("gn");
    if (usrGender == 1) {
        throw "current user gender is not defined";
    } 
    //max distance
    const maxDistance = request.user.get("maxD");
    const currentLoc = usrProfile.get("gp");

    //search type queries
    var specificQueries = []

    // ------------ dating specific query ------------ 
    const usrDate = usrProfile.get("da");
    //const usrOns = usrProfile.get("on");
    if (usrDate != null && maxDistance != null) {
        const dateQuery = new Parse.Query("PrsProfile");
        //distance in km
        dateQuery.withinKilometers("gp", currentLoc, maxDistance, false);
        
        //basic data
        dateQuery.lessThanOrEqualTo("ag", maxAge);
        dateQuery.greaterThanOrEqualTo("ag", minAge);
        
        if (partnerOri != null) {
            dateQuery.equalTo("o", partnerOri);
        }
        //dateQuery.equalTo("po", ori);
        dateQuery.containedIn("po", [null, ori]);
        //genders are defined for each specific query separately
        if (usrGender == 0) {
            //search for users looking for notDefined or women
            dateQuery.lessThanOrEqualTo("da", 1);
        } else if (usrGender == 2) {
            //search for users looking for notDefined or men
            dateQuery.greaterThanOrEqualTo("da", 1);
        }
        if (usrDate != 1) {
            //if current user defines specific gender, add it to constraints
            dateQuery.equalTo("gn", usrDate);
        } 
        if (usrProfile.get("on") == true) {
            dateQuery.equalTo("on", true);
        }
        //bloom filter
        //Inverted values - Array of "available Integers"
        //const bloomFilter = request.user.get("daBlm"); 

        //dummy generation of an array
        const bloomFilter = dummyArray(128000);

        //const bloomFilter = request.user.filter; 
        dateQuery.containedIn("bf", bloomFilter);

        //dateQuery.explain()

        const results = await dateQuery.find({ sessionToken: token });
        return results;
    } else {
        throw "not enough inputs to run a query";
    }
});

function generating fake “empty” bloom filter. Let’s called it “available bits array” as it is full from integers 0…128000 in above example for a fresh user that still did not remove any key.

function dummyArray(N) {
    var a=Array(N),b=0;
    while(b<N) a[b++]=b;
    return a;
}

when user creates account, it creates User document and Profile document, where in the profile document should be a field with hashed objectId into a one Int… that is the “bf” field in dateQuery.containedIn("bf", bloomFilter);

apart from the bloom filter validation containedIn/By it behaves as intended. I am just a bit confused how to check that any value in “bf” is not in the bloomFilter array.

  • It works well when there is only one Int in “bf” (not array) and using containedIn.
  • It scans the document if I check containedIn(“bf”, bloomFilter) where “bf” is and array of [Int, Int]
  • containedBy does produce for me strange behaviour as seen in my last post. It feels more like I should feed an array of ranges to it and it will return documents where “bf” values are within or outside these ranges…