iterate over large mongodb collection for purpose of updating schema - mongodb

I have a 300k collection of test docs. I want to update all persons firstName and lastName to be lowercase.
const person = new Schema({
firstName: { type: String},
lastName: { type: String }
})
I've added lowecase:true to the schema but how do I update the existing documents?
I tried:
CaseFile
.find({ })
.cursor()
.eachAsync(async function (doc) {
await doc.save()
})
but i get the error
Error: Collection method find is synchronous
I also tried :
CaseFile
.find({ })
.then(docs => {
docs.forEach(doc => {
doc.save()
})
})
which gives the error:
JavaScript heap out of memory
db version v5.0.2
"mongoose": "^6.0.5",
thank you Wernfried Domscheit for the pipeline 🏄 solution:
CaseFile.updateMany({}, [
{
$set:
{
firstName: { $toLower: '$firstName' },
lastName: { $toLower: '$lastName' }
}
}]
)
.then(res => res)

Why on earth "iterate", i.e. line by line?
Use an aggregation pipeline:
db.CaseFile.updateMany({}, [
{ $set:
firstName: { $toLower: "$firstName" },
lastName: { $toLower: "$lastName" }
}
])

Related

findOneAndUpdate document with array

Two questions here.
What is the correct way to findOneAndUpdate when there is an array? The example below errors with err MongooseError [CastError]: Cast to embedded failed for value.
Should you arrays of objects become separate collections?
* Example *
var ProductSchema = new Schema({
_id: Schema.Types.ObjectId,
product_name: String
});
var purchaseOrderSchema = new Schema(
{
purchaseOrderNo: Number,
products: [ProductSchema]
}
);
const purchaseOrder = new PurchaseOrder(req.body);
PurchaseOrder.findOneAndUpdate(
{ _id: req.body._id },
{
$set: req.body,
$push: req.body.products
},
{ upsert: true, new: true }
)
.then((result) => {
console.log('result', result);
res.status(200).json(result);
})
.catch((err) => {
console.log('err', err);
res.status(500).json({ error: err });
});
const body = {
_id: 'skjdhflksjdf',
purchaseOrderNo: 1,
products: [
{
_id: '111',
product_name: 'Cup'
},
{
_id: '222',
product_name: 'Spoon'
}
]
}
In the ProductSchema the type of _id field to set to ObjectId. The product id 111 and 222 are not a valid ObjectId and it fails to cast it. You can update the type of _id in ProductSchema to Number for this to work
var ProductSchema = new Schema({
_id: Number,
product_name: String
});

deleteMany not working with $size operator

I am trying to delete all collections where nested document "nested" has 0 element.
Tag.deleteMany({ "blog": { $size: 0 } }).exec()
For some reason, it doesn't work with Mongoose,
but it works when I run it this in Robo
db.getCollection('tags').deleteMany({ "blog": { $size: 0 } })
Anybody knows why it works in the query shell but not with Mongoose code?
Here's the Schema.
var tagSchema = new mongoose.Schema({
tag: String,
created: { type: Date, default: Date.now },
blog: [{ type: mongoose.Schema.Types.ObjectId,
ref: "blog" }]
var blogSchema = new mongoose.Schema({
title: String,
image: String,
description: String,
body: String,
created: { type: Date, default: Date.now },
tag:[{ type: mongoose.Schema.Types.ObjectId,
ref: "tag" }]
UPDATE... I think it is now Promise chaining issue?
let foundBlog
Blog.findOne({ title: '1st Post' })
.then((blog) => {
foundBlog = blog;
})
.then(() => {
console.log(foundBlog.tag)
Tag.updateMany(
{ _id : { $in: foundBlog.tag} },
{ $pull: { blog: foundBlog._id.toString()} }).exec()
})
.then(() => {
Tag.deleteMany({ "blog": { $size: 0 } }).exec()
})
.then(() => done())
For some reason Tag.deleteMany did not work after Tag.updateMany.
Is my promise chain correct? Thanks
Try chaining this way:
let foundBlog
Blog.findOne({ title: '1st Post' })
.then((blog) => {
foundBlog = blog;
})
.then(() => {
console.log(foundBlog.tag)
Tag.updateMany(
{ _id : { $in: foundBlog.tag} },
{ $pull: { blog: foundBlog._id.toString()} })
.then(() => {
Tag.deleteMany({ "blog": { $size: 0 } }).then(() => done())
})
})

Why do I get array of nulls in my database? [duplicate]

This question already has answers here:
Node.js Mongoose.js string to ObjectId function
(9 answers)
Closed 4 years ago.
I have an array of ids which is launchIds.
I'm trying to push it on a model field trips with
$addToSet: { trips: { $each: launchIds }. This gives me an error: Cast to [ObjectId] failed for value \"[\"1\",\"2\",\"3\"]\...
if I try to map through launchIds and convert to Mongoose.Shema.Types.ObjectId I get in the database trips: [null,null,null]
lauchIds = ['1','2','3']
async bookTrips({ launchIds }) {
let userId = "5bf7f7b3817119363da48403";
const mongoIds = launchIds.map(l => Mongoose.Schema.Types.ObjectId(l));
return this.store.User.findByIdAndUpdate(
{ _id: userId },
{
$addToSet: { trips: { $each: mongoIds } }
},
{ new: true }
);
}
Here's my model Schema:
const UserSchema = new Mongoose.Schema(
{
email: {
type: String,
required: true
},
token: String,
trips: [
{
type: Mongoose.Schema.Types.ObjectId,
ref: "trip"
}
]
},
{ timestamps: true }
);
I'm passing ids via grapql playground. Here's my mutation:
bookTrips: async (_, { launchIds }, { dataSources }) => {
console.log(launchIds);
// logs ['1','2','3']
console.log(typeof launchIds);
//Object
const results = await dataSources.userAPI.bookTrips({ launchIds });
console.log(results);
return { message: "hello" };
}
To convert a string or a number into mongo object use Mongoose.Types.ObjectId,
const mongoIds = launchIds.map(l => Mongoose.Types.ObjectId(l));
I was getting back an array of strings where this should be numbers
The solution:
My model (same as above):
const UserSchema = new Mongoose.Schema(
{
email: {
type: String,
required: true
},
token: String,
trips: [
{
type: Mongoose.Schema.Types.ObjectId,
ref: "trip"
}
]
},
{ timestamps: true }
);
crud API:
async bookTrips({ launchIds }) {
let userId = "5bf7f7b3817119363da48403";
const idsToNums = launchIds.map(Number);
const mongoIds = idsToNums.map(l => Mongoose.Types.ObjectId(l));
return this.store.User.findByIdAndUpdate(
{ _id: userId },
{
$push: { trips: { $each: mongoIds } }
},
{ new: true }
);
}
Notice the Mongoose.Schema.Types.ObjectId on model and Mongoose.Types.ObjectId on api. If I remove Schema from model or add Schema to api I'm getting an error. Not sure why, but the above example works. I hope someone will find this helpful or suggests a better solution.

Mongoose pull ObjectId from array

i'm trying to do a pretty simple operation, pull an item from an array with Mongoose on a Mongo database like so:
User.update({ _id: fromUserId }, { $pull: { linkedUsers: [idToDelete] } });
fromUserId & idToDelete are both Objects Ids.
The schema for Users goes like this:
var UserSchema = new Schema({
groups: [],
linkedUsers: [],
name: { type: String, required: true, index: { unique: true } }
});
linkedUsers is an array that only receives Ids of other users.
I've tried this as well:
User.findOne({ _id: fromUserId }, function(err, user) {
user.linkedUsers.pull(idToDelete);
user.save();
});
But with no luck.
The second option seem to almost work when i console the lenghts of the array at different positions but after calling save and checking, the length is still at 36:
User.findOne({ _id: fromUserId }, function(err, user) {
console.log(user.linkedUsers.length); // returns 36
user.linkedUsers.pull(idToDelete);
console.log(user.linkedUsers.length); // returns 35
user.save();
});
So it looks like i'm close but still, no luck. Both Ids are sent via the frontend side of the app.
I'm running those versions:
"mongodb": "^2.2.29",
"mongoose": "^5.0.7",
Thanks in advance.
You need to explicitly define the types in your schema definition i.e.
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
linkedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
and then use either
User.findOneAndUpdate(
{ _id: fromUserId },
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
or
User.findByIdAndUpdate(fromUserId,
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
I had a similar issue. I wanted to delete an object from an array, using the default _id from mongo, but my query was wrong:
const update = { $pull: { cities: cityId }};
It should be:
const update = { $pull: { cities: {_id: cityId} }};

Waterline: How to perform IN queries if attribute is a collection?

In the docs of waterline it is stated that this is the way to perform a IN query on a model:
Model.find({
name : ['Walter', 'Skyler']
});
And this the way to perform an OR query on a model:
Model.find({
or : [
{ name: 'walter' },
{ occupation: 'teacher' }
]
})
My problem now is that i need a combination of those two, and to make it even more complicated, one of the attributes i have to use is a collection.
So what i tried is this, but it doesn't seem to work:
Product.find({
or : [
{ createdBy: userIds },
{ likes: userIds }
]
})
Note: userIds is an array of id's from a user model.
The (simplified) product model looks likes this:
module.exports = {
attributes: {
name: 'string',
description: 'string',
createdBy: {
model: 'User'
},
brand: {
model: 'Brand',
},
likes: {
collection: 'User',
}
}
}
The query works when I only include createdBy, so it seems to be a problem with the collection attribute.
Is this somehow possible?
Thank you for your input.
UPDATE:
I think this is only possible with native() queries.
The way I understand it something like this should work.
Product.native(function(err, products){
if(err) return res.serverError(err);
products.find({"likes": { $elemMatch: { _id: { $in: userIds}}}}).toArray(function(err, results){
if (err){
console.log('ERROR', err);
}
else {
console.log("found products: " + results.length);
console.log(results);
return res.ok(results);
}
});
});
Unfortunately, it doesn't. The returned results is always an empty array.