我的文档结构如下。我的问题是如何在数据库端进行嵌套部分“角色”验证。我的要求是:
如果创建了角色,则存在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
}
},
]
}
有人可以建议如何进行嵌套文档验证吗?
答案 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 }
这是“婴儿时期”的一个功能,有点实验性,因此未来版本可能会解决此问题。
但是现在,您更好的选择是在客户端代码中执行此类“架构验证”(您将获得更好的异常报告)。已经存在许多采用该方法的库。