Yes, you can check all the subdocuments in the document by denying $elemMatch , and you can make sure the size is not 1. This is, of course, not very! And not quite obvious.
> db.createCollection('users', { ... validator: { ... name: {$type: 'string'}, ... roles: {$exists: 'true'}, ... $nor: [ ... {roles: {$size: 1}}, ... {roles: {$elemMatch: { ... $or: [ ... {name: {$not: {$type: 'string'}}}, ... {created_by: {$not: {$type: 'string'}}}, ... ] ... }}} ... ], ... } ... }) { "ok" : 1 }
This is confusing, but it works! This means only documents in which neither the size of roles is 1, nor roles has an element with name that is not string or created_by , which is not string .
This is based on the fact that in logical terms
for all x: f (x) and g (x)
Is equivalent
does not exist x st: not f (x) or not g (x)
We should use the latter, since MongoDB gives us only the existence operator.
Proof
Valid documents work:
> db.users.insert({ ... name: 'hello', ... roles: [], ... }) WriteResult({ "nInserted" : 1 }) > db.users.insert({ ... name: 'hello', ... roles: [ ... {name: 'foo', created_by: '2222'}, ... {name: 'bar', created_by: '3333'}, ... ] ... }) WriteResult({ "nInserted" : 1 })
If there is no field in roles , it is not executed:
> db.users.insert({ ... name: 'hello', ... roles: [ ... {name: 'foo', created_by: '2222'}, ... {created_by: '3333'}, ... ] ... }) WriteResult({ "nInserted" : 0, "writeError" : { "code" : 121, "errmsg" : "Document failed validation" } })
If the field in roles is of the wrong type, it is not executed:
> db.users.insert({ ... name: 'hello', ... roles: [ ... {name: 'foo', created_by: '2222'}, ... {name: 'bar', created_by: 3333}, ... ] ... }) WriteResult({ "nInserted" : 0, "writeError" : { "code" : 121, "errmsg" : "Document failed validation" } })
If roles has a size of 1, it fails:
> db.users.insert({ ... name: 'hello', ... roles: [ ... {name: 'foo', created_by: '2222'}, ... ] ... }) WriteResult({ "nInserted" : 0, "writeError" : { "code" : 121, "errmsg" : "Document failed validation" } })
The only thing I cannot understand, unfortunately, is to ensure that the roles are an array. roles: {$type: 'array'} seems to fail, I suppose because it actually checks that the elements are of type 'array' ?