7

Collection:

[
    { _id: "Foo", flag1: false, flag2: true, flag3: false },
    { _id: "Bar", flag1: true, flag2: false, flag3: true }
]

My question is, is it possible to call a method inside aggregate query?

aggregate({
    $project: {
        '_id': 1,
        'status' MyService.getStatus($flag1, $flag2, $flag3)
    }
});

If it is possible, what is the syntax of it? Result:

[
    { _id: "Foo", status: 'ok' },
    { _id: "Bar", status: 'broken' }
]

In my real world application I have 10 boolean flags per document. If the user gets this documents I would like to convert the flags and give them a meaning (for the user). E.g. consider a document represents a tire.

flag1 = true means tire have good pressure, false means low pressure
flag2 = true means depth of tire profile is good, false means little profile
and so on

So in summary I would like to say a tire is OK if

 flag1, flag2 are true and flag3 is false

and a tire needs to be replaced (BROKEN or REPLACE) when

flag1, flag2 are false and flag3 is true

When a document is returned to the user the flags should be removed. Instead we have the status field that says the tire is either OK or BROKEN.

DarkLeafyGreen
  • 69,338
  • 131
  • 383
  • 601
  • No this is not how this works. BSON Documents only. People get confused by issuing things like this in the shell and not realizing it evaluates first. What do you actually want here which is why you "think" you need a function? Possibly to determine is **one** of those fields is true? – Neil Lunn Sep 16 '14 at 10:07
  • @NeilLunn The function checks the flags with a boolean expression and returns a string. I have about 10 of this boolean flags in my document and I would like to bring them all together. "Possibly to determine is one of those fields is true?" How would this look like? – DarkLeafyGreen Sep 16 '14 at 10:12
  • Still not sure what you are trying to do here. Why is the first document "ok" and the second "broken"? Better to explain yourself be editing your question, then you can notify with a comment that you have done so. Clearly we need to "implement your logic" in how the aggregation framework does it. But the question is "what is the logic behind the returned values?" – Neil Lunn Sep 16 '14 at 10:15

4 Answers4

7

External functions don't work with the aggregation framework. Everything is parsed to BSON on input, so no JavaScript or anything else is allowed. This is all basically processed from BSON "operator" definition to native C++ code implementation so it is really fast.

What this comes down to is "converting" your expected logic to what the aggregation framework can process. There are in fact "logical" operators such as $or and $and that work in this context:

db.collection.aggregate([
    { "$project": {
       "_id": 1,
       "status": {
           "$cond": [
               { "$or": [
                   // Your first set of rules requires "false" for "flag1" or 
                   // "flag2" and "true" for "flag3"
                   { "$and": [
                       { "$not": [
                           { "$or": [ "$flag1", "$flag2" ] },
                       ]},
                       "$flag3"
                   ]},
                   // Your second set of rules requires "true" for "flag1" or 
                   // "flag2" and "false" for "flag3"
                   { "$and": [
                       { "$or": [ "$flag1", "$flag2" ] },
                       { "$not": [ "$flag3" ] }
                   ]},
               ]},
               "ok",
               "broken"
           ]
       }
    }}
])

So no external functions, just implement the logic with the operators that the aggregation framework supplies. In addition to the basic logical implementations there is $not to "reverse" the ligic and $cond which acts as a "ternary" in order to provide a different result from true/false evaluation.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • My function is: given array of strings, count how many strings of this array are substrings of some field, like '$name'. How'd you make this logic with the provided operators? – matheuscscp Feb 26 '16 at 09:28
2

The aggregate call can be passed a callback function, which is called after the aggregation has completed.

function getValuesAndMessges( params, callback ) {

  db.collection.aggregate([
    { "$project": {
       "_id": 1,
       "flag1": { "$first": "$flag1" },
       "flag2": { "$first": "$flag2" },
       "flag3": { "$first": "$flag3" },
    }}
  ], function( err, results ) {
    if ( !err ) {
      result.forEach( result => {
        // process items in results here, setting a value
        // using the actual logic for writing message ...
        if( flag1 )
          result.message = "broken";
        else
          result.messsge = 'OK';
      });
    }
    callback(err, results);
  });
}

this way each of your aggregated items (based on your conditions / parameters) will have a message property (or whatever properties you choose to write) set and you can use them in your calling function.

Code Uniquely
  • 6,356
  • 4
  • 30
  • 40
1

Yes we can call function in aggregation project with simple way.

let getStatus = (flag) => {
    return flag=='ok' ? 'ok' :'broken';
}
aggregate({
    $project: {
        '_id': 1,
        'status': getStatus($flag3)
    }
});
Ashish Sharma
  • 446
  • 5
  • 14
1

Inserting a function to an aggregation pipeline is possible since mongoDB version 4.4. for example:

db.collection.aggregate([
  {
    $project: {
      status: {
        $function: {
          "body": "function (flagA, flagB, flagC) {if (flagA && flagB && !flagC) { return 'ok'} return 'broken'}",
          "args": [
            "$flag1",
            "$flag2",
            "$flag3"
          ],
          "lang": "js"
        }
      }
    }
  }
])

Playground example

nimrod serok
  • 14,151
  • 2
  • 11
  • 33