MongoDB nested document validation for subdocuments

I received a document structured as follows. My question is how to test the role role of the nested part on the database side. My requirements:

  • role size can be 0 or more than 1.
  • the presence of a name and created_by for the role if the role is created.

    { "_id": "123456", "name": "User Name", "roles": [ { "name": "mobiles_user", "last_usage_at": { "$date": 1457000592991 }, "created_by": "987654", "created_at": { "$date": 1457000592991 } }, { "name": "webs_user", "last_usage_at": { "$date": 1457000592991 }, "created_by": "987654", "created_at": { "$date": 1457000592991 } }, ] } 

At the moment, I am doing the following for those who do not have nested attributes:

 db.createCollection( "users", { "validator" : { "_id" : { "$type" : "string" }, "email" : { "$regex" : /@gmail\.com$/ }, "name" : { "$type" : "string" } } } ) 

Can someone tell me how to check the attached documents?

+5
source share
2 answers

Edit : this answer is incorrect, you can check all subdocuments in the array. See Answer: fooobar.com/questions/1244699 / ...

You can not. You can do things like:

 "roles.name": { "$type": "string" } 

But all this actually means that “at least one” of these properties must match the specified type. This means that it is really valid:

 { "_id" : "123456", "name" : "User Name", "roles" : [ { "name" : "mobiles_user", "last_usage_at" : ISODate("2016-03-03T10:23:12.991Z"), "created_by" : "987654", "created_at" : ISODate("2016-03-03T10:23:12.991Z") }, { "name" : "webs_user", "last_usage_at" : ISODate("2016-03-03T10:23:12.991Z"), "created_by" : "987654", "created_at" : ISODate("2016-03-03T10:23:12.991Z") }, { "name" : 1 } ] } 

This is an afterall documentation check and by its nature is not very suitable for subdocuments in arrays or any data in the containing array.

The core of the implementation relies on expressions available to query operators , and since MongoDB does not have any expressions in standard query expressions that are equivalent to "All array entries must match this value" without being directly specific, then it is impossible to express as a validator condition.

The only way to check the contents of the array, as in the query expression, uses $where , and this is indicated by not being available with the document confirmation option.

Even the $size operator available for queries must match a certain size value, and cannot use the equality condition. That way, you can “check” the strict size, but not the minimum size, if:

 "roles.0": { "$exists": true } 

This is a function of "infancy" and somewhat experimental, so there is a possibility that future releases may solve this problem.

But now your best option is to do such a “circuit check” in client-side code (where you will get much better exception reporting). There are many existing libraries that use this approach.

+6
source

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

+6
source

Source: https://habr.com/ru/post/1244699/


All Articles