Node.js driver "mongodb" implementation of findAndModify() - how to specify fields? - node.js

I'm trying to pop and retrieve an element out of an array stored in a document. I can't use $pop since it doesn't return the POPed element. I'm trying to use findAndModify() instead. It works in the shell console, but I'm having troubles getting it to work using the mongodb node.js driver (https://www.npmjs.org/package/mongodb).
my document structure looks like so:
{ _id: '1', queue: [1,2,3]}
In mongo shell, I do:
> db.collection.findAndModify({ query: { _id: 1 },
update: { $pop: { queue: -1 } },
fields: { queue: { $slice: 1 } }, new: false })
$slice ensures that the returning document shows the element that has just been poped. To clarify, I'm not interested in what is in the queue, I'm only interested in what I have just popped out of the queue.
returns:
< {_id: 1, "queue": [1]} // yes, it works!
Using the mongodb library, I don't know how to specify the $slice: 1, it doesn't seem to be supported in the options(?):
> db.collection('collection').findAndModify(
{ _id: 1 },
[],
{ $pop: { queue: -1 }, queue: { $slice: 1 } },
{ new: false },
function(error, results) {
if(error) {
console.log(error);
}
console.log(results);
}
);
returns:
< MongoError: exception: Field name duplication not allowed with modifiers
Basically - where should I put the "queue: {$slice: 1}" part in the nodejs query to make this work? Is it even supported in the node.js driver implementation?
Also, it doesn't seem like findAndModify() is meant to be used this way. If $pop was returning the POPed value, it would be ideal. Any suggestions on how to do this?
Thanks,
- Samir

It seems like that the node.js implementation does not support the 'fields' operand at all.
We've figured out this work around:
1) We store each element in it's own document, instead of an array within the same document.
2) Now findAndModify works like so:
db.collection('collection').findAndModify(
{}, // findAndModify will match it to the first document, if multiple docs are found
[],
{},
{remove: true, new: false }, // returns & removes document from collection
function(error, results) {
if(error) {
console.log(error);
}
console.log(results);
}
);
Some good links that helped us and might help you if you have a similar issue:
https://blog.serverdensity.com/queueing-mongodb-using-mongodb/
http://www.slideshare.net/mongodb/mongodb-as-message-queue

Related

Mongoose: Missing expected field "$search"

I am trying to execute a query which performs a full text search with mongoose. The filter object looks as follows and works using mongosh:
{
authorUid: 'X',
'$text': { '$search': 'searchStr' }
}
But, whenever trying to execute this with mongoose, I'm getting the following error:
MongoServerError: Missing expected field "$search"
The mongoose code is as follows:
let query = Collection.find(
filterObj,
{
_id: 1,
title: 1,
src: 1,
},
{ sort: { createdAt: -1 } },
);
I am quite certain the text search index in the collection is created without error. It is visible in MongoDB Compass and I don't think the query should have worked in mongosh if this wasn't the case.
To be complete, here is the command ran via mongosh:
db.collection.find(
{
authorUid: 'X',
'$text': { '$search': 'searchStr' }
},
{
_id: 1,
title: 1,
src: 1
},
{sort: {createdAt: -1}}
)
Is this one not equivalent with the one written for Mongoose? How can I perform this query using Mongoose?
-- EDIT --
Just to ensure the cause is not the filterObj, I tried doing the exact same thing with it hard coded. Using Mongoose, it looks as follows:
let query = Collection.find(
{
$text: {
$search: "math",
},
},
);
I'm still getting the same error. It is however still working via mongosh.
-- EDIT 2 --
I did get this to work using aggregates instead of .find. The following code returns the same as in the mongosh environment:
const res = await Collection.aggregate([
{
$match: {
$text: {
$search: "math",
},
},
},
]);
This is most definitely usable, but I'm still clueless as to why the initial query did not work.

MongoDB return nested array of objects minus a property within the object (also .then() doesn't work with aggregation)

I'm very new to mongodb and am having difficulty getting to a solution for my use case. For example I have the following document:
{
_id : ObjectId('5rtgwr6gsrtbsr6hsfbsr6bdrfyb'),
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A',
array: ['lots','off','stuff']
},
{
id : 2,
title: 'B',
array: ['even','more','stuff']
}
]
}
I'd like to have the following returned:
{
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A'
},
{
id : 2,
title: 'B'
}
]
}
I've tried various combinations of using findOne() and aggregate() with $slice and $project. With findOne(), if it worked at all, the who document would be returned. I am unable to test whether attempts at aggregating work because .then((ret)=>{}) promises don't seem to work in node.js for me with it (no issues with findOne). Calling a function like so
return db.myCollection.aggregate([
{
$match: {
_id:ObjectId(mongo_id)
}
},
{
$project : {
mainArray: {
id:1,
title:1
}
}
}
],function(err,res){
console.log(res)
return res
})
logs the entire function and not the droids I'm looking for.
You're missing toArray() method to obtain the actual result set. Instead you're returning the aggregation cursor object. Try this.
return db.myCollection.aggregate([matchCode,projectCode]).toArray().then(
data => {
console.log(data);
return data;
},
error => { console.log(error)});
The documnetation on aggregation cursor for MongoDB NodeJS driver can
be found here
http://mongodb.github.io/node-mongodb-native/3.5/api/AggregationCursor.html#toArray
This is an alternative solution (to the solution mentioned in the comment by #v1shva)
Instead of using aggregation you can use projection option of .findOne() operation.
db.myCollection.findOne(matchCode, {
projection: { _id: false, 'mainArray.array': false } // or { _id: -1, 'mainArray.array': -1 }
})

findandmodify search 2 ID in collection and add a push Nodejs

an array in both, only it works I join the other not. I work this way. there any way to fix this inconvenience?
collection:findAndModify({
query: {
$in: [req.user._id, idunique]
},
update: {
$push: {
Amigos: {
usuario: idUserAccept,
name: fotoUserAccept,
img: fotoUserAccept
}
}
},
new: true
}).success(function(doc){
res.json(doc);
}).error(function(err){
console.log(err);
});
seem that you have mistake about query syntax.
findAndModify is Deprecated. Use findOneAndUpdate instead
check document in here

Set field if doesn't exist, push to array, then return document

I was hoping to do this in one operation, with just hitting the database once... but I don't know if it's possible with the api's.....
what I want is to:
find the document by id(which always will exist)
add object if it doesn't already exist { dayOfYear: 3, dataStuff: [{time:
Date(arg), data: 123] }
push {time: Date(arg), data: 123] } to dataStuff array
return modified document
I cooked up something along the lines of
return this.collection.findOneAndUpdate(dataDoc,
{ $set: { dayOfYear: reqBody.dayOfYear ,
$addToSet: { dataStuff: { time: Date(reqBody.date), data: reqBody.data }
}
but no success
The update object needs "seperate" top level keys for each atomic operation:
return this.collection.findOneAndUpdate(
dataDoc,
{
"$set": { dayOfYear: reqBody.dayOfYear },
"$addToSet": {
"dataStuff": { "time": Date(reqBody.date), "data": reqBody.data }
}
},
{ "returnOriginal": false }
)
With .findOneAndUpdate() from the core API you also need to set the "returnOrginal" option to false in order to return the modified document. With the mongoose API, it is { "new": true } instead.
In this syntax, both calls are returning a "Promise" to be resolved, and not just a direct response.
If you want to check if the whole object exists, you'll have to compare all the properties, like
this.collections.update({
_id: dataDoc,
dayOfYear: {
$ne: reqBody.dayOfYear
},
dataStuff: {
$elemMatch: {
time: {
$ne: Date(arg)
},
data: {
$ne: reqBody.data
}
}
}
}, {
$set: {
dayOfYear: reqBody.dayOfYear,
},
$addToSet: {
dataStuff: {
time: Date(arg),
data: reqBody.data
}
}
});
This way, you ensure that you always update one or zero collection items. The first argument is the query that either returns no elements or a single element (because _id is there), and if it returns one element, it gets updated. Which is, I believe, exactly what you need.

mongodb select either both documents or none

I am in situation where I have to update either two documents or none of them, how is it possible to implement such behavior with mongo?
// nodejs mongodb driver
Bus.update({
"_id": { $in: [ObjectId("abc"), ObjectId("def")] },
"seats": { $gt: 0 }
}, {
$inc: { "seats": -1 }
}, { multi: true }, function(error, update) {
assert(update.result.nModified === 2)
})
The problem with code above it will update even if only one bus matched. In my case I try to book ticket for bus in both directions and should fail if at least one of them already fully booked.
Thank you

Resources