I've been trying to get this running for a while now but I can't figure out what I'm doing wrong.
I have two schemas like this
const paymentSchema = new Schema({
year_month: {
type: String,
required: true
},
status: {
type: Boolean,
required: true
}
});
const testSchema = new Schema({
name: {
type: String,
required: true
},
payments: [{
type: paymentSchema,
required: false,
}]
});
Then I want to update the existing value or if that value is not available I'd like to add it to the array.
Let's say I have this values in the DB:
[
{
"_id": "5e90ae0e0ed9974174e92826",
"name": "User 1",
"payments": [
{
"_id": "5e90c3fb79bba9571ae58a66",
"year_month": "2020_02",
"status": false
}
]
}
]
Now I'd like to change the status of year_month 2020_02 to true with this code and it works:
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_02' }}
},
{ $set: {
'payments.$': {
year_month: '2020_02',
status: false
}
}
},
{
new: true,
upsert: true
}
).then( result => {
response.send(result);
});
The problem appears when I try to do this
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
},
{ $set: {
'payments.$': {
year_month: '2020_03',
status: false
}
},
},
{
new: true,
upsert: true
}
).then( result => {
response.send(result);
});
I get this message from the upsert...
(node:8481) UnhandledPromiseRejectionWarning: MongoError: The positional operator did not find the match needed from the query.
at Connection.<anonymous> (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/pool.js:466:61)
at Connection.emit (events.js:223:5)
at Connection.EventEmitter.emit (domain.js:475:20)
at processMessage (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/connection.js:384:10)
at TLSSocket.<anonymous> (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/connection.js:553:15)
at TLSSocket.emit (events.js:223:5)
at TLSSocket.EventEmitter.emit (domain.js:475:20)
at addChunk (_stream_readable.js:309:12)
at readableAddChunk (_stream_readable.js:290:11)
at TLSSocket.Readable.push (_stream_readable.js:224:10)
at TLSWrap.onStreamRead (internal/stream_base_commons.js:181:23)
(node:8481) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:8481) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
According to the docs Mongoose.findOneAndUpdate() this should work but I'm making some mistake, and I can't figure out what exactly. I know the match query is the issue but I'm not sure how to change it so the upsert gets applied.
In the end I solved it like this:
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
},
{
$set: {
'payments.$': {
year_month: '2020_02',
status: false
}
}
},
{new: true}
).then( success => {
// response === null if no match is found
if( success ) {
response.send(success);
} else {
testSchema.findOneAndUpdate(
{ _id: '5e90ae0e0ed9974174e92826' },
{
$push: {
'payments': request.body
}
},
{new: true}
).then(success => {
response.send(success);
});
}
},
error => {
response.send(error);
}
);
But I'm making two requests here which could cause the race condition issues. 1. to update and 2. to add if it doesn't exist
I'd like to know if there is a better way to make it use the upsert and avoid the race conditions.
There is also a nice short tutorial on mongoose page which describes the upsert on findOneAndUpdate but it doesn't include arrays and this is probably what complicates the issue in my case.
Final solution based on the responses from joe & prasad_. Actually it is not that complicated once you take the time to understand what's going on here.
testSchema.findOneAndUpdate(
{ "_id": customerId },
[{
$set: {
payments: {
$cond: [
{
$gt: [
{
$size: {
$filter: {
input: "$payments",
cond: {
$eq: [
"$$this.year_month",
testData.payments.year_month
]
}
}
}
},
0
]
},
{
$reduce: {
input: "$payments",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[{
$cond: [
{ $eq: ["$$this.year_month", testData.payments.year_month] },
{ $mergeObjects: ["$$this", { status: testData.payments.status }] },
"$$this"
]
}]
]
}
}
},
{
$concatArrays: [
"$payments",
[testData.payments]
]
}
]
}
}
}],
{ new: true }
).then(
success => {
response.send(success);
},
error => {
response.send(error);
}
);