MongoInvalidArgumentError: Update document requires atomic operators - mongodb

I am going through a course on MongoDB. Below is my list of documents in a collection called 'flightData'. Below is result of find query:
flights> db.flightData.find()
[
{
_id: ObjectId("611aaa1c4a0269583c8d81b0"),
aircraft: 'AirBus A308',
departingFrom: 'Mumbai',
arrivalStation: 'Moscow',
departureDate: '24/09/2021',
departureTime: '01:44',
arrivingAt: '12:00',
isOneWay: false,
status: {
description: 'on time',
lastUpdated: 'One hour ago',
details: { contact: 'John Doe' }
}
},
{
_id: ObjectId("611aaa554a0269583c8d81b1"),
aircraft: 'AirBus A308',
departingFrom: 'Kolkata',
arrivalStation: 'Stockholm',
departureDate: '24/09/2021',
departureTime: '01:44',
arrivingAt: '12:00',
isOneWay: false,
status: {
description: 'on time',
lastUpdated: 'One hour ago',
details: { contact: 'Cool User' }
}
}
]
When they show the difference between update and updateMany through an example similar to below one:
flights> db.flightData.update({_id:ObjectId("611aaa554a0269583c8d81b1")},{"delayed":false})
In the lecture it works. However, in my case it throws below error:
MongoInvalidArgumentError: Update document requires atomic operators
Can someone please explain this behavior? Is it not supported for my version or something else?
I am using MongoDB 5.0.2, mongosh 1.0.5

If you want to add the "delayed" field to the document you will want to use the $set operator
db.flightData.update({_id:ObjectId("611aaa554a0269583c8d81b1")},{$set:{"delayed":false}})
If you would like to replace the document you should use replaceOne
This command was added in mongodb 3.2 to avoid accidentality replacing the entire document when you mean to update a field

This may help someone so writing as answer.
In my case, I'm doing bulk write operation and missed a field that I'm trying to update in Mongoose Schema caused this issue. After correcting the Mongoose Schema, issue is resolved.
So, validate the fields that are used in update are configured in Schema if you are using Mongoose.

Related

Mongoose findOne not working as expected on nested records

I've got a collection in MongoDB whose simplified version looks like this:
Dealers = [{
Id: 123,
Name: 'Someone',
Email: 'someone#somewhere.com',
Vehicles: [
{
Id: 1234,
Make: 'Honda',
Model: 'Civic'
},
{
Id: 2345,
Make: 'Ford',
Model: 'Focus'
},
{
Id: 3456,
Make: 'Ford',
Model: 'KA'
}
]
}]
And my Mongoose Model looks a bit like this:
const vehicle_model = mongoose.Schema({
Id: {
Type: Number
},
Email: {
Type: String
},
Vehicles: [{
Id: {
Type: Number
},
Make: {
Type: String
},
Model: {
Type: String
}
}]
})
Note the Ids are not MongoDB Ids, just distinct numbers.
I try doing something like this:
const response = await vehicle_model.findOne({ 'Id': 123, 'Vehicles.Id': 1234 })
But when I do:
console.log(response.Vehicles.length)
It's returned all the Vehicles nested records instead on the one I'm after.
What am I doing wrong?
Thanks.
This question is asked very frequently. Indeed someone asked a related question here just 18 minutes before this one.
When query the database you are requesting that it identify and return matching documents to the client. That is a separate action entirely than asking for it to transform the shape of those documents before they are sent back to the client.
In MongoDB, the latter operation (transforming the shape of the document) is usually referred to as "Projection". Simple projections, specifically just returning a subset of the fields, can be done directly in find() (and similar) operations. Most drivers and the shell use the second argument to the method as the projection specification, see here in the documentation.
Your particular case is a little more complicated because you are looking to trim off some of the values in the array. There is a dedicated page in the documentation titled Project Fields to Return from Query which goes into more detail about different situations. Indeed near the bottom is a section titled Project Specific Array Elements in the Returned Array which describes your situation more directly. In it is where they describe usage of the positional $ operator. You can use that as a starting place as follows:
db.collection.find({
"Id": 123,
"Vehicles.Id": 1234
},
{
"Vehicles.$": 1
})
Playground demonstration here.
If you need something more complex, then you would have to start exploring usage of the $elemMatch (projection) operator (not the query variant) or, as #nimrod serok mentions in the comments, using the $filter aggregation operator in an aggregation pipeline. The last option here is certainly the most expressive and flexible, but also the most verbose.

Using a $match and/or reference fields in DocumentDB causes Aggregation not supported error

I have a what I consider to be a pretty straight forward search. I am developing it on MOngoDB locally but deploying it for DocumentDB on AWS. The function that I've written looks something like this:
if (searchCategory) {
return [
{
$match: {
$or: [
{ title: params.searchString },
{ description: params.searchString },
{ workingNotes: params.searchString },
],
active: true,
},
},
];
}
This query used to not have the $match statement around it and it worked fine, but something that we wanted to do was to use reference fields in our model for searching linked items as well. So we have something like this in our model:
appendix: [{type: Schema.Types.ObjectId, ref: "document"}],
Now when I fire off the search query, I get an error back saying "Aggregation stage not supported: '$lookup on multiple join conditions and uncorrelated subquery.'"
Adding the reference field and the $match were the only changes made to the functionality, which worked before we added those.
Based off the documentation I've read about DocumentDB, the $match operator is supported, so it's possible I am missing something else about how to structure the query or handle the reference fields but I've been unable to determine what that might be.
Any help would be appreciated. Thanks!

MongoDB - how to only update field if field does not exist

How can I update a mongo document with the following requirements:
Find a document by email property:
If the document exists:
If both retrieved and new document have property A, keep property A (the retrieved one).
If retrieved document property A is null or undefined or doesn't exist, update using property A of the new object.
If the document doesn't exist
Insert the new document.
The findOneAndUpdate seems not to convey the both 3 of the requirements. Thanks.
My recommendation is to go the following path:
db.getCollection('<some-collection>').update(
{ email: 'someguy#email.com' },
{
$set: {
name: "some guy",
username: someguy,
tel: '1234'
}
},
{ upsert: true }
);
Check upsert documentation:
https://docs.mongodb.com/manual/reference/method/db.collection.update/#upsert-option
Lets go through your requirements now:
3. If the document doesn't exist, insert the new document.
Yes, it will insert new document to collection if it doesnt find the document by email. Resulting document will be combination of find condition + $set + autogenerated _id, so it will look something like this:
{
_id: ObjectId(...)
email: 'someguy#email.com'
name: "some guy",
username: someguy,
tel: '1234'
}
2. If retrieved document property A is null or undefined or doesn't exist, update using property A of the new object.
All properties provided in $set will unconditionally be persisted in the database, which also covers your requirement of updating null/undefined values
3. If both retrieved and new document have property A, keep property A (the retrieved one).
If both newly provided A and database A are the same, we dont have a problem.
If As are different, dont you want to store the new A value?
If you are afraid of nulls/undefined values, you can omit them before providing object to $set.
What is the use-case for you not wanting to update database property with newly provided value?
One use-case i can see is that you want to pass createdAt in case you are creating new record, but dont want to update that value for existing records.
If thats the case, and you know those properties in advance, you can use $setOnInsert update operator. https://docs.mongodb.com/manual/reference/operator/update/#id1
So your update query can look like this:
db.getCollection('<some-collection>').update(
{ email: 'someguy#email.com' },
{
$set: {
name: "some guy",
username: someguy,
tel: '1234'
},
$setOnInsert: {
createdAt: new Date(),
updatedAt: new Date()
}
},
{ upsert: true }
);
I hope this helps!
You need not retrieve the document for updating the property A. You can use the update API of mongo to do so. Please find the psuedo code below:
db.<collection>.update({
"$or": [
{ "PropertyA": { "$exists": false } },
{ "PropertyA": null }
]
}, {$set: {"PropertyA": "NewValue"}});
The above code is for one property, but I think you can figure out how to scale it up.
Hope this helps !!

Sailsjs find where field is not set

I have a project in SailsJs, using waterline with a mongodb database.
I have some fields defined, which are not required fields.
I need to search records where that field is not defined. For example:
{id: 10, name: "jj"}
{id: 11, name: "kk", surname: "sr"}
I want to search where surname is not defined. Anybody has a clue how to do this?
I have tried
{surname: null}
{surname: ''}
Nothing produces the desired result.
I think what you want is Model.find( {where: {surname: null } }) Check out this link to Waterline Query language documentation
I think what you want is $exists: false. It is currently not supported by waterline. You can use Model.native() to use it.
Waterline native()
It returns MongoDB collection and you can use there native MongoDB operations:
MongoDB $exists
Complete code will look more like this
Model.native(function(err, collection) {
if (err) throw err;
collection.find({
surname: { $exists: false}
}).toArray(yourCallback);
});

mongodb: upserting: only set value if document is being inserted

Considering a simple mongo document structure:
{ _id, firstTime, lastTime }
The client needs to insert a document with a known ID, or update an existing document. The 'lastTime' should always be set to some latest time. For the 'firstTime', if a document is being inserted, then the 'firstTime' should be set to current time. However, if the document is already created, then 'firstTime' remain unchanged. I would like to do it purely with upserts (to avoid look ups).
I've crawled the http://www.mongodb.org/display/DOCS/Updating, but I just don't see how that particular operation can be done.
I don't believe this is something unreasonable, there are $push and $addToSet operations that effectively do that on array fields, just nothing that would do the same on simple fields. It's like there should be something like $setIf operation.
I ran into the exact same problem and there was no simple solution for <2.4 however since 2.4 the $setOnInsert operator let's you do exactly that.
db.collection.update( <query>,
{ $setOnInsert: { "firstTime": <TIMESTAMP> } },
{ upsert: true }
)
See the 2.4 release notes of setOnInsert for more info.
I ran into a very similar problem when attempting to upsert documents based on existing content--maybe this solution will work for you also:
Try removing the _id attribute from your record and only use it in the query portion of your update (you'll have to translate from pymongo speak...)
myid = doc.get('_id')
del doc['_id']
mycollection.update({'_id':myid}, {'$set':doc}, upsert=True)
If you will trigger the following code 2 subsequent times, it will first set both firstVisit and lastVisit on document insert (and will return upsertedId in the response) and on the second it will only update lastVisit (and will return modifiedCount: 1).
Tested with Mongo 4.0.5 though I believe should be working with older versions.
db.collection.updateOne(
{_id: 1},
{
$set: {
lastVisit: Date.now()
},
$setOnInsert: {
firstVisit: Date.now()
}
},
{ upsert: true }
);
There's no way to do this with just one upsert. You'd have to do it as 2 operations - first try to insert the document, if it already exists the insert will fail due to duplicate key violation on the _id index. Then you do an update operation to set the lastTime to now.