Search code examples
mongodbmongodb-query

MongoDB - Compare two fields inside schema validator


I want to create a query schema validator for my MongoDB collection, as explained here: https://www.mongodb.com/docs/manual/core/schema-validation/specify-query-expression-rules/

Two example documents could look like this:

{
  _id: "1",
  arr: [
    {a: 10},
    {a: 12, b: 15}
  ]
},
{
  _id: "2",
  arr: [
    {a: 12, b: 10},
    {a: 15}
  ]
}

I want all documents, where arr exists, to only have arr elements that either satisfy a > b, or do not have b. In the given example above, the first document would be invalid, because the second element of arr has b and does not satisfy a > b.

The first query that came to my mind is the following:

{
  $or: [
    {
      arr: {$exists: 0}
    },
    {
      arr: {
        $elemMatch: {
          $or: [
            {
              b: {$exists: 0}
            },
            {
              $expr: {$gt: ["$a", "$b"]}
            }
          ]
        }
      }
    }
  ]
}

Sadly, this does not work. Apparently, $expr cannot be used inside $elemMatch.

I cannot use aggregations, because the schema validator can only use the query operator.

What is the alternative to $expr inside $elemMatch?


Solution

  • Below is my interpretation of the validation rules:

    1. Check arr field
      a. if arr doesn't exist, the document pass
      b. if arr is an empty array, the document pass
      c. if arr contains element(s), proceed to step 2
    2. For all elements in the arr,
      a. if the element does not have the field b, this element pass
      b. if the element has the field b, it requires a to be larger than b to pass

    For step 2, the document only passes when all elements pass.

    So, it translates to below $expr:

    {
      $expr: {
        $or: [
          {
            $eq: [
              0,
              {
                $size: {
                  $ifNull: [
                    '$arr',
                    []
                  ]
                }
              }
            ]
          },
          {
            $allElementsTrue: {
              $map: {
                input: '$arr',
                as: 'elem',
                'in': {
                  $or: [
                    {
                      $eq: [
                        null,
                        {
                          $ifNull: [
                            '$$elem.b',
                            null
                          ]
                        }
                      ]
                    },
                    {
                      $gt: [
                        '$$elem.a',
                        '$$elem.b'
                      ]
                    }
                  ]
                }
              }
            }
          }
        ]
      }
    }
    

    Below is how it looks in my local MongoDB setup with your sample documents. You can see document 1 failed the validation while document2 passed MongoDB local setup