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'
?