Mongodb: 4.0.0, Mongoose: 5.3.0.
The $set part never udpates anything in my docs when I use updateMany (or simple update) with arrayFields and $[
Schema:
export const TableBookingSchema = new Schema({
tables: {
type: [
{
tableID: String,
numberOfChairs: Number,
reserved: Boolean,
location: String,
inDoors: Boolean,
reservedDuring: [
{
reservedFrom: Date,
reservedTo: Date,
reservedFor: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Consumer'
}
}
]
}
],
required: false
},
});
interface ITableBooking extends mongoose.Document {
tables: any[];
}
export const TableBooking = mongoose.model<ITableBooking>('TableBooking', TableBookingSchema);
update function:
TableBooking.updateMany({}, {
'$set': {
'tables.$[].reservedDuring.$[arr].reservedFrom': Date.now()
}
}, { arrayFilters: [{
'arr.reservedFrom': { $gte: Date.now() }
}],
multi: true
}, ).then(function (doc)){}
The result is: {"n":2,"nModified":0,"ok":1}. For some reason no matter which array element I want to update (reservedFrom, reservedTo, reservedFor), nothing ever changes, even tough I have 2 docs that meet the conditions in the arrayFilters section.
Sorry in advance if I missed something.
@vkarpov15 should mongoose cast in arrayFilter? Or should we add a note to the docs about it?
@Jugis mongoose doesn't cast the date in the arrayFilters option for you, and Date.now() returns a number, so the arrayFilter doesn't match anything. You'll need to change your $gte
value to a date with new Date()
or new Date(Date.now())
and it should work.
Here's a complete repro script that shows it working:
#!/usr/bin/env node
'use strict';
const assert = require('assert');
const mongoose = require('mongoose');
const { Schema, connection} = mongoose;
const GH = 'gh7041';
const URI = `mongodb://localhost:27017/${GH}`;
const OPTS = { family: 4, useNewUrlParser: true };
const schema = new Schema({
tables: {
type: [{
reservedDuring: [
{
reservedFrom: Date,
reservedTo: Date
}
]
}]
}
});
const Test = mongoose.model('test', schema);
const test = new Test({
tables: [
{
reservedDuring: [{
reservedFrom: Date.now(),
reservedTo: Date.now() + 3600000
}]
},
{
reservedDuring: [{
reservedFrom: Date.now() + 3600000,
reservedTo: Date.now() + 2 * 3600000
}]
},
{
reservedDuring: [{
reservedFrom: Date.now() + 2 * 3600000,
reservedTo: Date.now() + 3 * 3600000
}]
},
]
});
const test1 = test.tables[0].reservedDuring[0].reservedFrom;
const test2 = test.tables[1].reservedDuring[0].reservedFrom;
const test3 = test.tables[2].reservedDuring[0].reservedFrom;
async function run() {
await mongoose.connect(URI, OPTS);
await connection.dropDatabase();
await Test.create(test);
const cond = {};
const update = {
$set: {
'tables.$[].reservedDuring.$[arr].reservedFrom': Date.now()
}
};
const opts = {
arrayFilters: [{ 'arr.reservedFrom': { $gte: new Date(Date.now()) } }],
};
const res = await Test.updateMany(cond, update, opts);
assert.strictEqual(res.nModified, 1);
const found = await Test.findOne({});
const found1 = found.tables[0].reservedDuring[0].reservedFrom;
const found2 = found.tables[1].reservedDuring[0].reservedFrom;
const found3 = found.tables[2].reservedDuring[0].reservedFrom;
assert.deepEqual(found1, test1);
assert.notDeepEqual(found2, test2);
assert.notDeepEqual(found3, test3);
console.log('All Assertions Pass.');
await connection.close();
}
run();
issues: ./7079.js
All Assertions Pass.
issues:
Oh wow it works!
I was convinced that my "opts" part was correct: I presumed that in "{"n":2,"nModified":0,"ok":1}" the "n" part indicated how many matches were found for the criteria in "arrayFilters".
Thank you so much, and sorry about the nonsensical question. :)
yeah, the "n" is the number of documents matched by the overall query condition. It's not nonsensical at all, I think it would be neat if mongoose cast that value to a date for us 馃憤
Sorry I missed this notification, it got lost in my inbox. Will investigate.
Confirmed, here's a repro of where array filter casting fails. We need to cast the arrayFilters
array.
const assert = require('assert');
const mongoose = require('mongoose');
mongoose.set('debug', true);
const GITHUB_ISSUE = `gh7079`;
const connectionString = `mongodb://localhost:27017/${ GITHUB_ISSUE }`;
const { Schema } = mongoose;
run().then(() => console.log('done')).catch(error => console.error(error.stack));
async function run() {
await mongoose.connect(connectionString);
await mongoose.connection.dropDatabase();
const schema = new Schema({ comments: [{ date: Date }] });
const Model = mongoose.model('Test', schema);
await Model.create({
comments: [
{ date: new Date('2018-06-01') },
{ date: new Date('2017-06-01') },
{ date: new Date('2018-06-02') }
]
});
await Model.updateMany(
{},
{ $set: { 'comments.$[x].author': '2018-01-01' } },
{ arrayFilters: [{ 'x.author': { $gte: '2018-01-01' } }] }
);
console.log(await Model.findOne());
}
Most helpful comment
@vkarpov15 should mongoose cast in arrayFilter? Or should we add a note to the docs about it?
@Jugis mongoose doesn't cast the date in the arrayFilters option for you, and Date.now() returns a number, so the arrayFilter doesn't match anything. You'll need to change your
$gte
value to a date withnew Date()
ornew Date(Date.now())
and it should work.Here's a complete repro script that shows it working:
7079.js
Output: