Change type of value in collection using update - mongodb

I am looking for a way, how to update multiple documents in MongoDB. I want to modify similar structure like this one:
{[
"_id": 'mongo_id',
"name": "Name"
]}
to the structure like this, basically just change string attribute to object attribute with string property :
{
"_id": 'mongo_id',
"name": {
"type_1": "Name",
"type_2": ""
}
}
Is there a way how to do it in single mongo query or I have to create some kind of worker for example in node.js?

If you do not have any schemas involved to put constrains on your collections or if you have and name is defined as mixed type (from mongoose types as an example) then you can do whatever you want to any of the properties other than _id.
for example this update will change name to the object you want:
db.getCollection('<collectionName>').update({
_id: "mongo_id")
}, {
name: {
"type_1": "Name",
"type_2": ""
}
})

It looks like the best solution is create little worker to get all documents and update them in collection. I used node.js and mongodb npm package to create worker similar to this one:
var mongo = requiere('mongodb');
mongo.connect(process.env.MONGO_URL, function(err, database) {
if (err) {
throw err;
}
db = database;
db.collection('<collectionName>').find({}).toArray(function(err, array) {
if (err) {
console.log(err);
return process.exit(1);
}
console.log('length:', array.length);
var promises = [];
array.forEach(item => {
promises.push(db.collection('<collectionName>').update(
{
_id: item._id
},
{
'$set': {
'name': {
'type_1': item.name,
'type_2': '',
}
}
}))
});
return Promise.all(promises).then(function() {
console.log('Done');
return process.exit(0);
}).catch(function(err) {
console.log('err:', err);
return process.exit(1);
});
});
});

Related

graphql.GraphQLSchema: what type of argument to use to grab the query to pass to mongo db.collection.find to resolve the query

I'm learning ho to develop GraphQL service with express, express-graphql, **graphql, mongoose,
db.collection.find has an optional query parameter that specifies selection filter using query operators.
I wonder if it is possible to define a schema in which to define an argument for a query field that ultimately it is passed as it is to the collection find methods.
for example I expect that the graphql query:
{ todosQuerable(query: {title: "Andare a Novellara"})
{ _id, title, completed }
}
responds with:
{
"data": {
"todos": [
{
"title": "Andare a Novellara",
"completed": false
}
]
}
}
since in mongo
> db.Todo.find({title: 'Andare a Novellara'})
{ "_id" : ObjectId("600d95d2e506988bc4430bb7"), "title" : "Andare a Novellara", "completed" : false }
I'm thinking something like:
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: <???????????????> },
},
resolve: (source, { query }) => {
return new Promise((resolve, reject) => {
TODO.find(query, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
I have made a few attempts but have not been able to get an idea of which type I should use in this case
ho help reproduce the problem here the source repository of my tests
Please note that this works fine:
todosByTitle: {
type: new graphql.GraphQLList(TodoType),
args: {
title: { type: graphql.GraphQLString },
},
resolve: (source, { title }) => {
return new Promise((resolve, reject) => {
TODO.find({title: {$regex: '.*' + title + '.*', $options: 'i'}}, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
but what I'm looking for is something more generic: I would like to grab graphql field argument named query and pass it as is to the the query parameter of the mongo collection find.
So the good news is you can do whatever you want. The bad news is that:
You have to do it yourself
You have to add every searchable field, so you'll probably end up with two copies of the Todo object here.
The type you're looking for is just a custom input object type like this:
Notice the GraphQLInputObjectType below is different from GraphQLObjectType.
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
todosQuerable: {
...
type: new graphql.GraphQLList(TodoType),
...
args: {
query: { type: TodoQueryType },
},
...
}
These two queries work great!
(this is me using aliases so I can make the same query twice in one call)
{
titleSearch: todosQuerable(query:{ title:"Buy orange" }) {
_id
title
completed
}
idSearch: todosQuerable(query:{ _id:"601c3f374b6dcc601890048d" }) {
_id
title
completed
}
}
Footnote:
Just to have it said, this is generally a GraphQL anti-pattern, as this is building an API based on your database choices, rather than as a client-driven API.
Regex Edit as requested:
If you're trying to do regular expression lookups, you have to figure out how to programmatically convert your strings into regular expressions. i.e. your input is a string ("/Novellara/"), but mongoose requires passing a RegExp to do wildcards (/Novellara/, no quotes).
You can do that a number of ways, but I'll show one example. If you change your input fields to use two properties of value & isExpression, like below, you can do it, but you have to specifically craft your query, since it's no longer just a passthrough.
var ExpressableStringInput = new graphql.GraphQLInputObjectType({
name: 'ExpressableString',
fields: {
value: {
type: graphql.GraphQLString
},
isExpression:{
type: graphql.GraphQLBoolean,
defaultValue: false,
}
}
})
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: ExpressableStringInput
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
// resolver
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: TodoQueryType },
},
resolve: async (source, { query }) => {
const dbQuery = {};
if (query.title.isExpression) {
dbQuery.title = new RegExp(query.title.value);
} else {
dbQuery.title = query.title.value;
}
return new Promise((resolve, reject) => {
TODO.find(dbQuery, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
your query would then look like
query {
todosQuerable(query:{ title: { value: "Buy.*", isExpression: true }}) {
_id
title
completed
}
}
This query makes sense in my mind. If I think about the form you would show to a user, there is probably an input box and a checkbox that says "is this a regular expression?" or something, which would populate this query.
Alternatively, you could do like string matching: if the first and last characters are "/", you automagically make it into a regex before passing it into mongoose.

Mongoose findOneAndUpdate with $addToSet pushes duplicate

I have a schema such as
listSchema = new Schema({
...,
arts: [
{
...,
art: { type: Schema.Types.ObjectId, ref: 'Art', required: true },
note: Number
}
]
})
My goal is to find this document, push an object but without duplicate
The object look like
var art = { art: req.body.art, note: req.body.note }
The code I tried to use is
List.findOneAndUpdate({ _id: listId, user: req.myUser._id },
{ $addToSet: { arts: art} },
(err, list) => {
if (err) {
console.error(err);
return res.status(400).send()
} else {
if (list) {
console.log(list)
return res.status(200).json(list)
} else {
return res.status(404).send()
}
}
})
And yet there are multiple entries with the same Art id in my Arts array.
Also, the documentation isn't clear at all on which method to use to update something. Is this the correct way ? Or should I retrieve and then modify my object and .save() it ?
Found a recent link that came from this
List.findOneAndUpdate({ _id: listId, user: req.user._id, 'arts.art': artId }, { $set: { 'arts.$[elem]': artEntry } }, { arrayFilters: [{ 'elem.art': mongoose.Types.ObjectId(artId) }] })
artworkEntry being my modifications/push.
But the more I'm using Mongoose, the more it feels they want you to use .save() and modify the entries yourself using direct modification.
This might cause some concurrency but they introduced recently a, option to use on the schema { optimisticConcurrency: true } which might solve this problem.

Trouble in inserting sub-documents using mongoose

I created an Enterprise database using mongoose in node-express project.Now I need to add employee sub document in the enterprise_employee field of the enterprise database, but it throws an error.
Following code snippet is my schema
var mongoose= require('mongoose');
var Enterprise= new mongoose.Schema({
enterprise_id:{
type:String
},
enterprise_name:{
type:String
},
enterprise_email:{
type:String
},
enterprise_employee: [{employee_id:Number, employee_name:String}]
});
module.exports={
Enterprise:Enterprise
};
This code snippet is the route for adding employee sub-document
var mongoose = require('mongoose');
var Enterprise = mongoose.model('Enterprise_gpy');
var addEmployee = function(req, res){
Enterprise.findOne({"enterprise_id":req.body.enterprise_id},function(err, res){
if(err){
console.log('NO SUCH ORGANISATION');
res.json(err);
} else {
Enterprise.enterprise_employee.push({
"employee_id": req.body.employee_id,
"employee_name":req.body.employee_name
});
}
});
}
module.exports={
addEmployee:addEmployee
};
This the error thrown
throw er; // Unhandled 'error' event
^ TypeError: Cannot read property 'push' of undefined
Seems like what you need is an update operation that uses the $push operator to add the elements to the array field. The following example demonstrates this:
Enterprise.findOneAndUpdate(
{ "enterprise_id": req.body.enterprise_id },
{
"$push": {
"enterprise_employee": {
"employee_id": req.body.employee_id,
"employee_name":req.body.employee_name
}
}
},
{ "new": true }, // return the modified document
function(err, enterprise) {
if (err) {
console.log('NO SUCH ORGANISATION');
res.json(err);
} else {
console.log(enterprise); // modified document
}
}
);
I think this is because your schema needs to define the enterprise_employee as an array. You have to explicitly tell Mongoose that it should be an 'Array' type.
Try this:
enterprise_employee: {
type: Array,
fields: [
{
employee_id: String,
employee_name: String
}
]
}

Distinct Query with Cloudant Connector using Loopback in API Connect/StrongLoop

I am trying to get distinct values for a query using Loopback with a Cloudant Connector, but I haven't found anything about this in the documentation.
e.g. I need a query to turn this:
[
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★☆☆"
},
{
rating: "★★★☆☆"
}
]
into this:
[
{
rating: "★★★★★"
},
{
rating: "★★★☆☆"
}
]
I'm using the REST API to query my Products model (above is a filtered view of just the rating field). If there is some sort of filter that I can use without modifying the server that I somehow just missed in the documentation, that would be the best choice.
Is there any way I can add a distinct field like:
/Products?filter[fields][rating]=true?distinct=true
or how can I go about solving this?
Also, I've seen another answer talking about adding a remote method to solve this (something like this for mySQL):
Locations.regions = function (cb) {
var ds = Locations.app.datasources.myDS;
var sql = "SELECT DISTINCT region FROM Locations ORDER BY region"; // here you write your sql query.
ds.connector.execute(sql, [], function (err, regions) {
if (err) {
cb(err, null);
} else {
cb(null, regions);
}
});
};
Locations.remoteMethod(
'regions', {
http: {
path: '/regions',
verb: 'get'
},
returns: {
root: true,
type: 'object'
}
}
);
If this would work, how would I implement it with the Cloudant NoSQL DB connector?
Thanks!
If your documents looked like this:
{
"name": "Star Wars",
"year": 1978,
"rating": "*****"
}
You can create a MapReduce view, which emits doc.rating as the key and uses the build-in _count reducer:
function(doc) {
emit(doc.rating,null);
}
When you query this view with group=true, distinct values of rating will be presented with counts of their occurrence in the data set.

How to update embedded document in mongoose?

I've looked through the mongoose API, and many questions on SO and on the google group, and still can't figure out updating embedded documents.
I'm trying to update this particular userListings object with the contents of args.
for (var i = 0; i < req.user.userListings.length; i++) {
if (req.user.userListings[i].listingId == req.params.listingId) {
User.update({
_id: req.user._id,
'userListings._id': req.user.userListings[i]._id
}, {
'userListings.isRead': args.isRead,
'userListings.isFavorite': args.isFavorite,
'userListings.isArchived': args.isArchived
}, function(err, user) {
res.send(user);
});
}
}
Here are the schemas:
var userListingSchema = new mongoose.Schema({
listingId: ObjectId,
isRead: {
type: Boolean,
default: true
},
isFavorite: {
type: Boolean,
default: false
},
isArchived: {
type: Boolean,
default: false
}
});
var userSchema = new mongoose.Schema({
userListings: [userListingSchema]
});
This find also doesn't work, which is probably the first issue:
User.find({
'_id': req.user._id,
'userListings._id': req.user.userListings[i]._id
}, function(err, user) {
console.log(err ? err : user);
});
which returns:
{ stack: [Getter/Setter],
arguments: [ 'path', undefined ],
type: 'non_object_property_call',
message: [Getter/Setter] }
That should be the equivalent of this mongo client call:
db.users.find({'userListings._id': ObjectId("4e44850101fde3a3f3000002"), _id: ObjectId("4e4483912bb87f8ef2000212")})
Running:
mongoose v1.8.1
mongoose-auth v0.0.11
node v0.4.10
when you already have the user, you can just do something like this:
var listing = req.user.userListings.id(req.params.listingId);
listing.isRead = args.isRead;
listing.isFavorite = args.isFavorite;
listing.isArchived = args.isArchived;
req.user.save(function (err) {
// ...
});
as found here: http://mongoosejs.com/docs/subdocs.html
Finding a sub-document
Each document has an _id. DocumentArrays have a special id method for looking up a document by its _id.
var doc = parent.children.id(id);
* * warning * *
as #zach pointed out, you have to declare the sub-document's schema before the actual document 's schema to be able to use the id() method.
Is this just a mismatch on variables names?
You have user.userListings[i].listingId in the for loop but user.userListings[i]._id in the find.
Are you looking for listingId or _id?
You have to save the parent object, and markModified the nested document.
That´s the way we do it
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Profile.findById(req.params.id, function (err, profile) {
if (err) { return handleError(res, err); }
if(!profile) { return res.send(404); }
var updated = _.merge(profile, req.body);
updated.markModified('NestedObj');
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, profile);
});
});
};