我已经编写了一些$redact
操作来过滤我的文档:
db.test.aggregate([
{ $redact: {
$cond: {
if: { "$ifNull" : ["$_acl.READ", false] },
then: { $cond: {
if: { $anyElementTrue: {
$map: {
input: "$_acl.READ",
as: "myfield",
in: { $setIsSubset: [ "$$myfield", ["user1“] ] }
}
}},
then: "$$DESCEND",
else: "$$PRUNE"
}},
else: "$$DESCEND",
}
}}
])
这将删除_acl.READ
不包含user1
的所有(子)文档。但它会保留未设置_acl.READ
的所有(子)文档。
在聚合之后,如果某些信息不是文档的一部分,我无法判断是否删除了某些信息。
虽然我想删除敏感信息,但保留一些暗示访问被拒绝的提示。即。
{
id: ...,
subDoc1: {
foo: "bar",
_acl: {
READ: [ ["user1"] ]
}
},
subDoc2: {
_error: "ACCESS DENIED"
}
}
我无法弄清楚如何在使用$redact
时修改文档。
谢谢!
答案 0 :(得分:4)
$redact
管道阶段在聚合框架中是非常独特的,因为它不仅能够递归地下降到文档的嵌套结构中,而且还能够遍历任何级别的所有键。但它仍然需要一个“深度”的概念,因为一个键必须包含一个子文档对象或一个本身由子文档组成的数组。
但它不能做的是“替换”或“换出”内容。此处允许的唯一操作是相当设置的,或者更具体地说是from the documentation:
参数可以是任何有效表达式,只要它解析为$$DESCEND,$$PRUNE或$$KEEP系统变量即可。有关表达式的更多信息,请参阅Expressions。
可能具有误导性的陈述是“论证可以是任何有效的表达式”,这实际上是正确的,但它必须返回完全与内容相同的内容无论如何,它将被解决出现在其中一个系统变量中。
因此,为了替换“编辑”内容提供某种“拒绝访问”响应,您必须以不同方式处理。此外,您还需要考虑其他操作符的限制,这些操作符可能无法以“递归”方式工作,或者需要如前所述的“遍历”方式。
保持文档中的示例:
{
"_id": 1,
"title": "123 Department Report",
"tags": [ "G", "STLW" ],
"year": 2014,
"subsections": [
{
"subtitle": "Section 1: Overview",
"tags": [ "SI", "G" ],
"content": "Section 1: This is the content of section 1."
},
{
"subtitle": "Section 2: Analysis",
"tags": [ "STLW" ],
"content": "Section 2: This is the content of section 2."
},
{
"subtitle": "Section 3: Budgeting",
"tags": [ "TK" ],
"content": {
"text": "Section 3: This is the content of section3.",
"tags": [ "HCS" ]
}
}
]
}
如果我们想在匹配[ "G", "STLW" ]
的“角色标记”时将其处理为“替换”,那么您可以执行以下操作:
var userAccess = [ "STLW", "G" ];
db.sample.aggregate([
{ "$project": {
"title": 1,
"tags": 1,
"year": 1,
"subsections": { "$map": {
"input": "$subsections",
"as": "el",
"in": { "$cond": [
{ "$gt": [
{ "$size": { "$setIntersection": [ "$$el.tags", userAccess ] }},
0
]},
"$$el",
{
"subtitle": "$$el.subtitle",
"label": { "$literal": "Access Denied" }
}
]}
}}
}}
])
这会产生这样的结果:
{
"_id": 1,
"title": "123 Department Report",
"tags": [ "G", "STLW" ],
"year": 2014,
"subsections": [
{
"subtitle": "Section 1: Overview",
"tags": [ "SI", "G" ],
"content": "Section 1: This is the content of section 1."
},
{
"subtitle": "Section 2: Analysis",
"tags": [ "STLW" ],
"content": "Section 2: This is the content of section 2."
},
{
"subtitle" : "Section 3: Budgeting",
"label" : "Access Denied"
}
]
}
基本上,我们宁愿使用$map
运算符来处理项目数组并将条件传递给每个元素。在这种情况下,$cond
运算符首先查看条件,以确定此处的“标记”字段是否包含我们之前定义的userAccess
变量的任何$setIntersection
结果。
如果该条件被视为true
,则该元素将被未更改地返回。否则在false
情况下,不是删除元素(不是$map
而是另一步),因为$map
返回与“输入”中收到的元素数量相等的元素,只需用其他内容替换返回的内容。在这种情况下,对象具有单个键和$literal
值。被“拒绝访问”。
所以请记住不能做的事情:
您实际上无法遍历文档密钥。任何处理都需要明确指出具体提到的密钥。
因此,内容不能是数组以外的其他形式,因为MongoDB无法遍历密钥。您需要在每个关键路径上有条件地进行评估。
正在过滤“顶级”文档。除非你真的想在最后添加一个额外的阶段:
{ "$project": {
"doc": { "$cond": [
{ "$gt": [
{ "$size": { "$setIntersection": [ "$tags", userAccess ] }},
0
]},
"$ROOT",
{
"title": "$title",
"label": { "$literal": "Access Denied" }
}
]}
}}
说完所有的话,除非你真的打算在一天结束时“聚合”一些东西,否则其中任何一个都没有太多的目的。只是让服务器对客户端代码中可以执行的文档内容进行完全相同的过滤,通常不能最好地利用昂贵的CPU周期。
即使在给出的基本示例中,在客户端代码中执行此操作也更有意义,除非您真正从删除不符合条件的条目通过网络传输中获得主要好处。在您的情况下,没有这样的好处,所以更好地对客户端代码。