Previously I had tried something like (with mongoose and promises):
var cursor = User.find({email: from.address, token: tokenMatches[1]});
and then
return cursor.update(
{'votes.title': b},
{'$set': { 'votes.$.category': a }}
).then(function (result) {
if(result.nModified == 0) {
return cursor.update({}, {'$push': { votes: { category: a, title: b }}}).then(function (res) {
ServerLog('updatePush', res);
return res;
});
}
});
But it always returned nModified = 0 for the first and second call. Until I found out that the cursor object actually has no update function. So why is it so? And why did it not throw an exception?
Model.find returns a Query object, not a cursor. Query does have an update method that lets you execute the query as an update operation.
Related
All mongodb transaction examples I have seen so far don't allow reading results back. For example (pseudo code):
begin_transaction
collection_one.update(...)
collection_two.update(...)
commit_transaction
The problem is, what if I want to update collection_two based on the result of updating collection_one?
For example?
begin_transaction
result = collection_one.update(...)
if (result.update_count() > 0)
{
collection_two.update(...)
}
commit_transaction
I have never seen an example like the above? It seems that when use transaction, I can't get the result back.
Another example,
begin_transaction
result = collection_children.find({name: 'xxx'})
collection_team.delete({name in result})
commit_transaction
Basically, I want to perform a find on a collection, and based the find result to perform a second action on a different collection.
And I want the 2 actions together be atomic.
Here is an example of how this works as expected with Mongoose. The same example obviously is possible without Mongoose.
var author = new Author({ email: "test22#test.com", other: [{a: 1}});
var book = new Book({ title: 'ABC' })
let doFoo = async () => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const opts = { session, new: true };
let _author = await author.save() // Notice we get back the record
let _book = await book.save()
// Notice we are using now the new id of the just saved record
await Author.findOneAndUpdate({ _id: _author.id }, { $set: { other: { foo: 2 } }}, opts);
await Book.findOneAndUpdate({ _id: _book.id }, { $set: { title: "ABC" }}, opts);
await session.commitTransaction();
session.endSession();
} catch (error) {
await session.abortTransaction();
session.endSession();
throw error; // Rethrow so calling function sees error
}
}
doFoo()
So in the example above we create/save two different records in their respective collections and after that based on the new records we go back and update them.
I'm trying to project out only the matched element of an array, in the updated version. But I'm getting the error: "MongoError: >1 field in obj: { _id: 0, lotes.$: 1 }"
If I remove 'new: true', it works. But then I have the doc before the update. And I would really like the updated version.
What's wrong? How can I fix it?
The Offer doc is something like:
{
_id
series: [ Serie ]
}
Serie structure is something like:
{
_id
public.available: Number
public.expDate: Date
}
I'm using Mongoose:
var query = {
'_id': offerId,
'series': {
$elemMatch: {
'_id': serieId,
'public.available': {$gt:0},
'public.expDate': {$gt: now}
}
}
};
var update = {
$inc: { 'series.$.public.available' : -1 }
};
var options = { // project out just the element found, updated
new:true,
select: {
'_id': 0,
'series.$': 1
}
};
Offers.findOneAndUpdate(query, update, options)
.then( element => {
...
}
For anyone else experiencing this error, it is also the most common error when trying to perform an illegal action such as trying to update a database element inside of a findOne request.
Making sure your request is correct, such as findOneAndUpdate should be your first port of call when you get this error.
As Anthony Winzlet pointed out in the links, there seems to be an issue with Mongoose, in which if you use 'new:true', you can't project out the $elemMatch.
So my solution was to keep using 'new:true' only, without projections. And reduce the array later on to get the $elemMatch:
.then( (result) => {
var aux = result.series.reduce((acu, serie, index) => {
if (serie._id == req.params.serieId) return index;
});
var element = result.series[aux];
}
I'm trying to implement a "range query" in MongoDB using Mongoose, ordered by a 'criteria' and then by '_id'.
And I would like to return to the client a string containing both cursors.
I was trying to implement something like the code below, with the commented block 2. However, I'm getting an error. Not even the log messages are being printed.
In my test, the query is empty, because the collection is empty.
I suspected that I was not getting the cursor, so I've tested with 'block 1' instead of block 2, and it worked.
But since I need the last cursor, I guess what I really need to use is the .toArray method, right?
What am I doing wrong?
Feed.find({
"criteria": {$lt: cursorCriteria},
"_id": {$lt: cursorId}
})
.sort({
criteria: -1,
_id: -1
})
.limit( 50 )
// block 1: just to test if I'm getting the cursor
.then( items => {
items.forEach( function(item) {
console.log('an item');
})
})
/* block 2: if I try this block instead of block 1, I get an error
.toArray( items => {
if (items.length > 0) {
console.log('not empty);
} else {
console.log('empty');
}
var nextCursor = '${item.criteria}_${item._id}';
res.status(200).json({item, nextCursor});
})
*/
Mongoose doesn't a have toArray() method
This worked fine:
.then( items => {
if (items.length > 0) {
console.log('not empty');
} else {
console.log('empty');
}
var nextCursor;
if (items.length > 0) {
nextCursor = '' + items[items.length-1].criteria + "_" + items[items.length-1]._id;
} else {
nextCursor = '';
}
res.status(200).json({items, nextCursor});
})
How can I tell when findOneAndUpdate successfully updates a document? huh variable always returns the same thing (whether id is in the database or not) and doc is always null.
var query = {id : id };
var huh = schemaModel.findOneAndUpdate(query, obj, function(doc) {
console.log(doc);
if(doc) {
callback(doc);
} else {
errback('');
}
}
);
console.log(huh);
You are only passing one parameter to the callback in your findOneAndUpdate query.
I think that your query succeeds, but doc will always come null when you successfully update the object as it is the first parameter which is the err.
Also, I do not see the code for your callback function, so I am just presuming that it can be accessed in the scope of your function.
var query = {id : id };
var huh = schemaModel.findOneAndUpdate(query, obj, function(err, doc) {
if(err) {
return "Error spotted!";
} else {
return "Found & Updated";
}
}
);
console.log(huh);
By returning those values, you are basically assigning them to the huh variable and it should log accordingly. It serves as a logging mechanism.
I have an aggregation query on a students collection that is returning two sets of results
for each student like this
{ _id: 1543,
name: 'Bill Jackson',
scores: { type: 'homework', score: 38.86823689842918 } }
{ _id: 1543,
name: 'Bill Jackson',
scores: { type: 'homework', score: 15.861613903793295 } }
That's working fine. Now in the callback I want to remove one of the scores for each student. I use ugly nested conditionals below to isolate which of the two records I want to remove, and, once that's achieved I create a find and Modify query to remove the doc but there's no evidence of it getting run. Neither the error or success callback to the findAndModify are getting run, however I am able to log that I'm inside the area where the findAndModify is getting called.
Is it possible to query the db in the callback to an aggregation? If not, how should I perform an operation that persists in the db?
//aggregation query ommitted
, function(err, result) { //callbackstarts here with result of aggregation query that returns two records for each student
for (var i=0; i<result.length; i++) {
var id = result[i]['_id'];
if (id === result[i]['_id']){
if (foo && foo === result[i]['_id']){
//if we're in here, we know we need to remove score associated with this result[i]['_id']
//create findAndModify to remove the record
var query = { '_id' : result[i]['_id']}
var sort = []
var operation = { '$pull' : { 'scores.score' : result[i]['scores']['score'] } };
var options = []
console.log('this code is getting called but findAndModify not')
db.collection('students').findAndModify(query, sort, operation, options,function(err, doc) {
if(err) throw err;
if (!doc) {
console.log("record not found");
}
else {
console.log("changed doc" + doc);
}
});
}else {
var foo = result[i]['_id'] //part of logic to isolate which of two records to remove
}