Validate a mongodb query syntax programmatically - node.js

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

Related

How do I make a conditional query to a mongodb database?

I'm trying to make a filtering component for my website and I want to be able to get that data which they specified through the filter from the MongoDB database. I'm using MERN stack and want to conditionally send a query to the database to retrieve based on whether or not it's empty or not. If it's empty, it means the user wants anything in that specific category and not one specific thing so I don't want to send an empty query to the DB which is just going to look for an empty string/array and realize that nothing in the DB has an empty string/array and won't return anything.
That's why I want to conditionally render my query parameter because that's the only workaround I can think of for my earlier question which does not have a solution for:
How can I send an object as a query and use the $all operator in MERN stack?
Here is the code and where I want the the conditional render to happen:
var t = ["People"]
const test = await Project.find( {$all: {
theme: t // have something that condditionally sends this, if t is empty then don't send it
}}
).sort({ createdAt: -1 })
console.log(test)
You can conditionally construct the search-filter:
const filter = {};
if (t.length) { // or whatever condition you need
filter.$all = {theme: t};
}
const test = await Project.find(filter) // rest of your code

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

MongooseJS: Hide Specific Documents From Query

I have a User schema with a activated field of Boolean type. I want queries to only return documents which have activated: true. And I hope there is a more efficient and DRY way of doing so than adding a conditional to every find, findOne, or findById.
What would be the most effective approach?
while there may be some way to do this, it is generally a bad idea to always hide this information.
speaking from experience trying to do this with other languages and database systems, you will, at some point, want / need to load items that are not actived. but if you always and only return activated items, you'll never be able to get the list you need.
for your purposes, i would recommend creating a findActive method on your schema:
someSchema.static("findActive", function(query, cb){
// check if there is a query and callback
if (!cb){
cb = query;
query = {};
}
// set up an empty query, if there isn't one provided
if (!query) { query = {}; }
// make sure you only load activated items
query.activated = true;
// run the query
this.find(query, cb);
});
with this method, you will have a findActive method the same as findOne, but it will always filter for activated items.
MyModel.findActive(function(err, modelList){ ... });
and it optionally supports additional query filters
MyModel.findActive({some: "stuff"}, function(err, modelList){ ... });
You might want to look at Mongoose Query middleware here
Query middleware is supported for the following Model and Query
functions.
count
find
findOne
...
For example:
User.pre('find', function() {
console.log(this instanceof mongoose.Query); // true
this.activated = true;
});

Run custom validation in mongoose update query

I have been trying to run a custom validator to check if the name entered by the user already exists in the database. Since, mongoDb treats uppercase and lowercase names as different, I created my own validator for it.
function uniqueFieldInsensitive ( modelName, field ){
return function(val, cb){
if( val && val.length ){ // if string not empty/null
var query = mongoose.models[modelName]
.where( field, new RegExp('^'+val+'$', 'i') ); // lookup the collection for somthing that looks like this field
if( !this.isNew ){ // if update, make sure we are not colliding with itself
query = query.where('_id').ne(this._id)
}
query.count(function(err,n){
// false when validation fails
cb( n < 1 )
})
} else { // raise error of unique if empty // may be confusing, but is rightful
cb( false )
}
}
}
Now, the problem is that the validator runs while saving the document in the DB but not while update.
Since, I am using mongoose version 4.x, I also tried using { runValidators: true } in my update query. That doesn't work either as the 'this' keyword in my validator is 'null' while in the case of update whereas it refers to the updated doc in the case of save.
Could you please let me know if there is something i missed or is there any other way by which I can run custom validators in update query.
Finally I found a way out to do this.
According to MongoDB documentation, it says:
First, update validators only check $set and $unset operations. Update validators will not check $push or $inc operations.
The second and most important difference lies in the fact that, in document validators, this refers to the document being updated. In the case of update validators, there is no underlying document, so this will be null in your custom validators.
Refer to : Validators for update()
So, now we are only left with calling save() instead of update() in our queries. Since, save() calls all the custom and inbuilt validators, our validator will also be called. I achieved it like this:
function(req, res, next) {
_.assign(req.libraryStep, req.body);
req.libraryStep.save().then(function(data){
res.json(data);
}).then(null, function (err) {
console.info(err);
var newErr = new errorHandler.error.ProcessingError(errorHandler.getErrorMessage(err));
next(newErr);
});
};
Notice here req.libraryStep is the document that i queried from the database. I have used lodash method assign which takes the updated json and assigns it to the existing database document.
https://lodash.com/docs#assign
I dont think this is the ideal way but as for now till Mongoose doesnt come up with supporting custom validators, we can use this to solve our problem.
This is a fairly old thread, but I wanted to update the answer for those who come across it like I did.
While you're correct about the context of this being empty in an update validator (per the docs), there is a context option you can use to set the context of this. See the docs
However, a plugin also exists that will check the uniqueness of the field you are setting: mongoose-unique-validator. I use this for checking for duplicate emails. This also has an option for case insensitivity, so I would check it out. It also does run correctly using the update command with the runValidators: true option.

mongoose: how to get string representation of query

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).

Resources