Это сложно понять, но здесь есть несколько веских причин, и случай документа normal MongoDB не будет включать такие "точечные поля" . Таким образом, требуется особая обработка.
Основной случай здесь заключается в том, что вам, по сути, необходимо преобразовать "key"
в документе, который содержит «пунктирное поле», чтобы фактически быть «строкой»,а затем просто ищите наличие "component"
в этой строке.
Короткий случай: вам нужно выражение pipeline
для вашего watch()
, например:
const pipeline = [
{ "$match": {
"$expr": {
"$ne": [
{ "$size": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ], }, -1]
}
}
}},
0
]
}
}}
];
Для этого используется $objectToArray
для преобразования объекта updatedFields
в массив свойств k
и v
вместо именованных ключей. На этом этапе результирующие значения будут выглядеть следующим образом:
[
{
"k": "coponents.weapon",
"v": "Sword"
}
]
Это позволяет использовать массив now с операцией $filter
, используя выражение из$indexOfCP
, который проверяет наличие строки в значении свойства k
. Если это что-то отличное от -1
(для не найдено ), то любой элемент, содержащий "component"
в этом значении, будет единственным сохраненным элементом и соответствующим элементом из массива результата.
ПРИМЕЧАНИЕ Если у вас MongoDB 4.2, вы можете обратиться к оператору $regexMatch
вместо $indexOfCP
. Это не должно быть необходимо для простого тестирования «присутствия» строки в строке, но Регулярные выражения, конечно, могут сделать немного больше, если ваш сценарий использования этого требует. от начала строки до включенной "точки", заменяя cond
на $filter
:
"$not": { "$regexMatch": { "input": "$$this.k", "regex": /^components\./ } }
, так как он возвращается в виде массива,Затем вы можете протестировать $size
, чтобы увидеть, действительно ли в массиве отфильтрованный действительно осталось что-то после удаления значений "component"
. Если это не так и размер действительно 0
, то результаты отбрасываются через $expr
, который также является основным оператором, позволяющим использовать выражения агрегации в пределах $match
также.
Конечно, все, что делает, это выбирает документы , которые действительно действительны для возврата. В случае, если у вас могут быть другие измененные поля в результате update
, указанном в документе «Поток изменений», тогда вам фактически нужно применить тот же тип операции $filter
, чтобы фактически удалить"component"
поля из результата:
{ "$addFields": {
"updateDescription": {
"updatedFields": {
"$arrayToObject": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ] }, -1 ]
}
}
}
}
}
}
}
Обратите внимание, здесь добавлено $arrayToObject
, которое по существу полностью изменяет используемый процесс манипуляции и затем фактически возвращаетupdatedFields
содержимое в исходном виде, без нежелательных ключей.
Для демонстрации приведен фактический полный список, который воспроизводит изменения, внесенные в коллекцию с такой структурой, и включает watcher конвейер для отключения нежелательных изменений:
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const options = { useNewUrlParser: true, useUnifiedTopology: true };
const log = doc => console.log(JSON.stringify(doc, undefined, 2));
(async function() {
try {
let client = await MongoClient.connect(uri, options);
let db = client.db('test');
// Insert some starting data
await db.collection('things').deleteMany();
await db.collection('things').insertOne({
components: {
weapon: { type: "Sword" },
health: { value: 10 }
},
type: "Monster"
});
// Set up the changeStream
const pipeline = [
// Filters documents
{ "$match": {
"$expr": {
"$ne": [
{ "$size": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ], }, -1]
/* Alternate MongoDB 4.2 syntax
"$not": {
"$regexMatch": {
"input": "$$this.k",
"regex": /^components\./
}
}
*/
}
}
}},
0
]
}
}},
/* -- Uncomment just to see the k and v structure
{ "$project": {
"update": { "$objectToArray": "$updateDescription.updatedFields" }
}}
*/
// Actually removes the keys and returns only non filtered
{ "$addFields": {
"updateDescription": {
"updatedFields": {
"$arrayToObject": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ] }, -1 ]
/* Alternate MongoDB 4.2 syntax
"$not": {
"$regexMatch": {
"input": "$$this.k",
"regex": /^components\./
}
}
*/
}
}
}
}
}
}
}
];
const changeStream = db.collection('things').watch(pipeline);
changeStream.on('change', next => log({ changeDocument: next }));
// Loop some changes
await new Promise(async (resolve, reject) => {
let tick = true;
setInterval(async () => {
try {
let { value }= await db.collection('things')
.findOneAndUpdate(
{},
{ $set: { 'components.weapon': (tick) ? 'Knife' : 'Sword' }},
{ returnOriginalDocument: false }
);
tick = !tick; // flip the boolean
log({ currentDoc: value });
} catch (e) {
reject(e);
}
},2000)
});
} catch (e) {
console.error(e)
}
})()