MongoDB嵌套了子文档的文档验证

时间:2016-03-08 23:56:53

标签: mongodb

我的文档结构如下。我的问题是如何在数据库端进行嵌套部分“角色”验证。我的要求是:

  • 角色大小可以是0或大于1.
  • 如果创建了角色,则存在name和created_by。

    #include <cstddef>
    #include <cstring>
    #include <iostream>
    
    #include "rapidxml.hpp"
    #include "rapidxml_utils.hpp"
    
    …
    
    rapidxml::file<> xml_file("/path/to/xml/file");
    rapidxml::xml_document<> xml_doc;
    xml_doc.parse<0>(xml_file.data());
    
    const rapidxml::xml_node<> *catalog_node = xml_doc.first_node("catalog");
    if (catalog_node == NULL) {
        std::cout << "No \"catalog\" node!" << std::endl;
        return;
    }
    
    for (const rapidxml::xml_node<> *book_node = catalog_node->first_node("book");
        book_node != NULL;
        book_node = book_node->next_sibling()) {
    
        const rapidxml::xml_attribute<> *id_attribute = book_node->first_attribute("id");
        if (id_attribute == NULL || strcmp(id_attribute->value(), "bk102") != 0) {
            continue;
        }
    
        const rapidxml::xml_attribute<> *value_attribute = book_node->first_attribute("value");
        if (value_attribute == NULL) {
            continue;
        }
    
        std::cout << "Found \"value\" attribute with value: " << value_attribute->value() << std::endl;
    }
    

目前,我只针对那些没有嵌套的属性执行以下操作:

{
  "_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
        }
    },
  ]
}

有人可以建议如何进行嵌套文档验证吗?

2 个答案:

答案 0 :(得分:7)

是的,您可以通过否定$elemMatch来验证文档中的所有子文档,并且您可以确保大小不是1.它确实不是很漂亮!而且也不是很明显。

> 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 }

这很令人困惑,但它确实有效!这意味着只接受roles的大小为1和roles的文件都没有name的元素,该元素不是string或{{} 1}}那不是created_by

这是基于逻辑术语

的事实
  

表示所有x:f(x)和g(x)

相当于

  

不存在x s.t。:不是f(x)或不是g(x)

我们必须使用后者,因为MongoDB只给我们一个存在的运算符。

证明

有效文件有效:

string

如果> 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 }) 中缺少某个字段,则会失败:

roles

如果> db.users.insert({ ... name: 'hello', ... roles: [ ... {name: 'foo', created_by: '2222'}, ... {created_by: '3333'}, ... ] ... }) WriteResult({ "nInserted" : 0, "writeError" : { "code" : 121, "errmsg" : "Document failed validation" } }) 中的字段类型错误,则会失败:

roles

如果> 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" } }) 的大小为1,则会失败:

roles

不幸的是,我唯一能弄清楚的是如何确保角色是一个数组。 > db.users.insert({ ... name: 'hello', ... roles: [ ... {name: 'foo', created_by: '2222'}, ... ] ... }) WriteResult({ "nInserted" : 0, "writeError" : { "code" : 121, "errmsg" : "Document failed validation" } }) 似乎一切都失败了,我推测是因为它实际检查的是元素属于roles: {$type: 'array'}类型?

答案 1 :(得分:6)

编辑:此答案不正确, 可以验证数组中的所有子文档。见答案:https://stackoverflow.com/a/43102783/200224

你不能真的。你可以这样做:

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

但真正意味着所有这些属性中的“至少一个”需要匹配指定的类型。这意味着这实际上是有效的:

{
    "_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
            }
    ]
}

它毕竟是"documement validation",并且本质上不太适合数组中的子文档,或者包含数组中的任何数据。

实现的核心依赖于query operators可用的表达式,并且因为MongoDB在标准查询表达式中缺少任何等于“所有数组条目必须匹配此值”而不直接特定的,那么它不可能表达为验证条件。

在“查询”表达式中检查数组内容的唯一可能性是使用$where,并且注意到它不是文档验证的可用选项。

即使可用于查询的$size运算符必须匹配特定的“大小”值,也不能使用不等的条件。所以你“可以”验证严格的尺寸,但不是最小尺寸,除非:

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

这是“婴儿时期”的一个功能,有点实验性,因此未来版本可能会解决此问题。

但是现在,您更好的选择是在客户端代码中执行此类“架构验证”(您将获得更好的异常报告)。已经存在许多采用该方法的库。