mongoose: how to get string representation of query - node.js

I implementing module who automatically generate mongoose query by requested params, so for simplified test process I need to be able to get text representation of final query. How could I do that?
Like we have something like this:
var q = AppModel.find({id:777}).sort({date:-1})
I need to get something like this
"db.appmodels.where({id:777}).sort({date: -1})"

You can set debug for mongoose, which would by default send the queries to console, to use the following:
mongoose.set('debug', function (collectionName, method, query, doc) {
// Here query is what you are looking for.
// so whatever you want to do with the query
// would be done in here
})

Given a query object q you can rebuild the query using its fields, namely q._conditions and q._update. This is undocumented though and could easily break between versions of Mongoose (tested on Mongoose 4.0.4).

Related

mongoose - select specific fields in Model.create

const generatedEvent = await Event.create(req.body);
res.send(generatedEvent);
I am getting some data from the request body and I can generate a new Event. And I'm returning the event to client when it has generated. But I don't want to return all fields with event. I want to make filter operation like how we are using select function like this: Event.find().select({title:1,description:1})
How can i use this select func with Model.create?
If you take a look at the mongoose-source code, you can see that Model.create returns a promise with the created/inserted documents. There's no way to specify a filtering-options to return only specific fields.
Of course you could do a .find() in combination with a .select() call after creating/inserting a new record but that would result in one extra DB-query for each insert which does not make a lot of sense.
You could instead just return the desired properties from the returned document, since you know that a new document was inserted successfully with the provided data, when the promise resolved. So you could simply do:
res.send({title: generatedEvent.title, description: generatedEvent.description});
Model.create() internally doesn't fetch the document from the database, rather it actually returns the result whether it's inserted successfully or not. If successful, mongoose will return the original mongoose document that mongoose created before sending to the database.
So you could just select the fields by yourself. Using es2015 Object destructuring assignment and Object shorthand property names would help writing more concise code.
const { title, description } = await Event.create(req.body); // Object destructuring
res.send({ title, description }); // Object shorthand property names

Validate a mongodb query syntax programmatically

I have an API method where the user can pass in their own query. The field in the collection is simply ns, so the user might pass something like:
v.search = function(query: Object){
// query => {ns:{$in:['foo','bar',baz]}} // valid!
// query => {ns:{$in:{}}} // invalid!
// query => {ns:/foo/} // valid!
});
is there some way to do this, like a smoke test that can fail queries that are obviously wrong?
I am hoping that some MongoDB libraries would export this functionality... but in all likelihood they validate the query only by sending it to the database, which is in fact, the real arbiter of which query is valid/invalid.
But I am looking to validate the query before sending it to the DB.
Some modules that are part of MongoDB Compass have been made open source.
There are two modules that may be of use for your use case:
mongodb-language-model
mongodb-query-parser
Although they may not fit your use case 100%, it should give you a very close validation. For example npm install mongodb-language-model, then:
var accepts = require('mongodb-language-model').accepts;
console.log(accepts('{"ns":{"$in":["foo", "bar", "baz"]}}')); // true
console.log(accepts('{"ns":{"$in":{}}}')); // false
console.log(accepts('{"ns":{"$regex": "foo"}}')); // true
Also may be of interest, npm install mongodb-query-parser to parse a string value into a JSON query. For example:
var parse = require('mongodb-query-parser');
var query = '{"ns":{"$in":["foo", "bar", "baz"]}}';
console.log(parse.parseFilter(query)); // {ns:{'$in':['foo','bar','baz']}}
I don't think it's possible to do otherwise than by reflecting query.ns object and checking every of its property / associated value

How to make object for mongoose query?

Is there way to build complete object for mongoose to use as query? When making route for search I need to pass many query parameters and sanitize them in express middleware. From those I would like to build query object.
I ended up with something like this:
Inside midleware:
res.locals.filter = {
query: ...,
projection: ...,
sort: ...,
limit: ....,
}
inside router:
User.find(res.locals.filter.query)
.sort(res.locals.filter.sort)
.limit(res.locals.filter.limit)
.exec()
Is there any way to format my filter so I can pass it all at once? Found some examples but nothing seem to work for me...
In other word do something like:
User.query(filter)
You can do this by adding query as a static method on your schema:
userSchema.statics.query = function(filter) {
return this.find(filter.query).sort(filter.sort).limit(filter.limit);
};
Which you could then call as:
User.query(res.locals.filter).exec(callback);
As it was pointed out by JohnnyHK, the style of find call with object fields like $query and $orderBy was only supported in the shell (and is now deprecated) so there isn't any built-in support.

node.js mongodb projection ignored when there is a criterion

I am writing a node.js application using express, mongodb, and monk.
When I do a find with criteria only or with projections only, I get the expected result, but when I do a find with both, the full documents are returned, i.e., the projection is not performed. My code looks like this:
var collection = db.get('myDB');
collection.find({field1: "value"},{field2: 1, _id: 0},function(e,docs) {
...do stuff with docs...
});
It returns not just field2 but all fields of all the docs matching the criterion on field1. I can get field2 from this, but I don't like the inefficiency of it.
Is there a way to use both criteria and projections?
Monk uses a space-delimited string for field projection where you prefix a field name with - to exclude it.
So it should be:
var collection = db.get('myDB');
collection.find({field1: "value"}, 'field2 -_id', function(e,docs) {
...do stuff with docs...
});

sorting alpha with mongoose

I'm trying to sort via mongoose 3.6.20 and I am receiving some unexpected results.
I have a list of companies with a name. At first I thought that maybe it was sorting in a case sensitive way. Which based on articles, I expect was true.
I'm now using a virtual property to down case the sort field. However, I'm still getting unexpected results.
CompanySchema.virtual('name_lower').get(function(){
return this.name.toLowerCase();
});
and when I sort
Company.find().sort({ name_lower: 1 });
I'm getting it in the following order:
company name
google
company name (yes a duplicate for testing)
I'm also outputting the value of my virtual property and it looks right. There is no whitespace or funky characters that would result in the 2nd 'company name' from appearing after google.
Using nodejs, express, mongoose.
What am I missing or doing incorrectly?
Update:
Based on the information provided in the answers, I refactored my schema to include some normalized fields and hooked into the pre save event of my document, where I update those normalized fields and sort using them in all future queries.
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
Next, is in the schema I use:
var CompanySchema = mongoose.Schema({
...
normalized_name: { type: String, set: normalize },
...
});
Where normalize is a function that for now, returns a lowercase version of the value passed into it. However, this allows me to expand on it later really fast, and I can quickly do the same to other fields that I might need to sort against.
As of MongoDB v3.4, case insensitive sorting can be done using collation method:
Company.find()
.collation({locale: "en" }) //or whatever collation you want
.sort({name:'asc'})
.exec(function(err, results) {
// use your case insensitive sorted results
});
Unfortunately MongoDB and Mongoose does not currently support complex sorting, so there are 2 options:
As you said, create a new field with the names sanitized to be all lowercase
Run a big for loop over all the data and update each company name to it's lower case form:
db.CompanyCollection.find().forEach(
function(e) {
e.CompanyName = e.CompanyName.toLowerCase();
db.CompanyCollection.save(e);
}
)
or
db.CompanyCollection.update({_id: e._id}, {$set: {CompanyName: e.CompanyName.toLowerCase()
Please see Update MongoDB collection using $toLower and Mongoose: Sort alphabetically as well for more info.
I want to put out that in this hook:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
You'll have to call next(); at the end, if you want the normalized_name to be saved in the database, so the pre save hook would look like:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
next();
});
This answer seems to be more helpful to me. I had to consider diacritics along with the case so I had used strength:3.
Mongoose: Sort alphabetically

Resources