Custom MongoDB search query - node.js

In my database I have documents which all contain the property foo. For each value of foo I have a function that either returns true or false. How can I query for all the documents for which the value of foo makes the function return true?

If you need to check if your string field's value is one of several, you need the $in modifier.
db.collection.find( { field : { $in : array } } );
It works fast and uses index (if possible).
If your field is an array and you pass a string, use this syntax.
db.collection.find({array_field : string_value});
It will check every element in the array and, if any of them matches your string, it will return the document.

You could use $where.
Example:
db.myCollection.find( { $where: "this.a > 3" });
db.myCollection.find( "this.a > 3" );
db.myCollection.find( { $where: function() { return this.a > 3;}});
Note, this is run in Javascript. This means two things.
You can put arbitrary Javacript into $where expression (the function form).
It'll be significantly slower than regular queries.

It really depends on what the function is and how you are using it. Is the function constant for any given record? Is it even a function you can evaluate on the database server? ...
In the extreme, if you need to check this value often, you might, for example, create a field that exists only when f(foo) is true and then create a sparse index on that field.
$where may well be the solution you are looking for, but depending on the access patterns there may be a better solution.

Related

Not able to use for loop in ternary operator in arangodb

How do we write conditions in arango, that includes for loops. I can elaborate the requirement below.
My requirement is if a particular attribute(array type) exists in the arango collection, i would read data from the collection(that requires a loop) or else, might do the following :
return null
return empty string ""
do nothing.
Is this possible to achieve in arango?
The helping methods could be -->
-- has(collectionname, attributename)
-- The ternary operator ?:
let attribute1 = has(doc,"attribute1") ?(
for name in doc.attribute1.names
filter name.language == "xyz"
return name.name
) : ""
But this dosent work. Seems like arango compiler first attempts to compile the for loop, finds nulls and reports error as below. Instead, it should have compiled "has" function first for the ternary operator being used.
collection or array expected as operand to FOR loop; you provided a value of type 'null' (while executing)
If there is a better way of doing it, would appreciate the advice!!
Thanks in advance!
Nilotpal
Fakhrany here from ArangoDB.
Regarding your question, this is a known limitation.
From https://www.arangodb.com/docs/3.8/aql/fundamentals-limitations.html:
The following other limitations are known for AQL queries:
Subqueries that are used inside expressions are pulled out of these
expressions and executed beforehand. That means that subqueries do not
participate in lazy evaluation of operands, for example in the ternary
operator. Also see evaluation of subqueries.
Also noted here for the ternary operator:
https://www.arangodb.com/docs/3.8/aql/operators.html#ternary-operator.
An answer to the question what to do may be to use a FILTER before enumerating over the attributes:
FOR doc IN collection
/* the following filter will only let those documents passed in which "attribute1.names" is an array */
FILTER IS_ARRAY(doc.attribute1.names)
FOR name IN doc.attribute1.names
FILTER name.language == "xyz"
RETURN name.name
Other solutions are also possible. Depends a bit on the use case.

AQL: Bind parameter on operator

Is there a way to have bind parameter on operator ("<", "<=" etc ...) ? I'm working on a Foxx service.
Example :
const operator = '<'
const res = query`
FOR v IN myCollection
FILTER v.value ${operator} ${maxValue}
`
I can do it with db._query :
const operator = '<'
const res = db._query('
FOR v IN myCollection
FILTER v.value ${operator} #maxValue'
{ maxValue: 100 })
Normal bind parameters (with one #) can only be used for the values null, true, false, numbers, strings, arrays and objects.
Collection bind parameters (with two ##) can be used where collection names are specified.
Passing an operator via bind parameters is not possible in AQL, as it could likely change the meaning of a query, or render it totally invalid.
Consider the following example:
FOR v IN myCollection
FILTER v.value #operator #maxValue
This query does not even parse, regardless of what values are passed in the bind parameters. And this is a good thing, because otherwise one may pass something like #operator: "abc", #maxValue: ">=", which would mean the query can be parsed fine without bind parameters, but would produce a parse error with bind parameters injected.
So the easiest solution here is to inject the comparison operator into the query via template string substituion, though of course you need to make sure the requested comparison operator is in a whitelisted of allowed operators.
But you would need to do this even with bind parameters, as otherwise people could just send #operator: "!=" or #operator: "NOT IN" or other operators which you either don't expect or that can make your query more expensive.

get list of collections having all words exists in field which added in given string in mongoDB

I want to search list of collections from mongoDB have all the keywords of given string.
For e.g.
I have a collection
{
"id":1
"text":"go for shopping",
"description":"you can visit this branch as well"
}
{
"id":2
"text":"check exiting discount",
"description":"We have various discount options"
}
Now, If I will pass string like "I want to go for shopping" w.r.t. text field in find query of mongoDB. Then I should get first collection as output because text field value "go for shopping" exists in the input string passed in find query.
This can be achieved through $text operator in MongoDB. But you have to createIndex on the "text" field in your database.(or whichever filed you want to be matched, I would suggest you rename it in your db to avoid confusion)
db.yourCollectionName.createIndex({"text":"text"})
The first field here is the "text" field in your database, and the second one is the mongo operator.
Then you can pass any query like,
db.yourCollectionName.find({$text: {$search: "I want to go for shopping"}})
The "$text" here is the mongo operator.
This would return all documents which have any of the keywords above.
Maybe you can read more around this and improvise and modify.
Ref: MongoDb $text
You can do so through regular expression. MongoDb provides the provision of matching strings through regex patterns.
In your case you could do something like:
db.yourCollectionName.find({text:{$regex:"go for shopping" }})
This will return you all the documents having the phrase "go for shopping" in the text field.
Ref: MongoDb Regex

How to extract values from this complex Puppet Struct

I have this data structure in puppet:
Struct[
'ssh_keys' => Hash[
String,
Struct[
'path' => String,
'content' => String,
]
]
] $myStructure
And I would like to extract all the 'path' values into an Array.
I got as far as mapping the inner Struct using
$testvariable = $myStructure['ssh_keys'].map |$items| { $items[1] }
But a bit suck here, any help would be much appreciated.
It's not clear what you're hung up on, as you are indeed most of the way to a solution that should work. For hashes, however, I do usually prefer the form of the map() function in which the lambda takes two parameters, a separate key and value. That will read more clearly in this case:
$testvariable = $myStructure['ssh_keys'].map |$unused, $ssh_key| { $ssh_key['path'] }
But you should also be able in your original code to index $items[1] as the hash (Struct) it is: $items[1]['path'].
You could also use the dig() function if you cannot abide the mixture of array and hash indexing in the above: $items.dig(1, 'path').

How to get all matches with MongoDB $elemMatch?

I have two objects:
{
genre: ['music', 'movie']
}
and
{
genre: ['movie', 'music']
}
and my query is:
db.test.find({genre :{ $elemMatch:{ $in : ['movie']}}})
and it only gives me the second object. Why? I want to get all the docs that contain a specific genre in their arrays no matter where in the array. How can I do this?
You need neither $elemMatch nor $in in this case. A simple field:value will match documents where field is an array and any one of the values in that array is value. That means
db.test.find({genre :'movie'});
will suffice.
The array query operators are required in more complex situations.
$in is needed when you have a list of possible values and want documents where any of them is found (so db.test.find({genre : { $in:['movie']} }); would work, but would be needlessly convoluted)
$all works like $in but requires that all provided elements are in the array
$elemMatch is a bit more complex. It is required when you want to use multiple operator-conditions (like $gt or $lt) but want only those documents where one array entry matches all the conditions. Without the $elemMatch operator, you get results where each condition is met by at least one array entry, but not necessarily all by the same entry.
Remember db.test.find() in general returns you cursor object. You can access all doc as follows:
entry = db.test.find({genre :{ $elemMatch:{ $in : ['movie']}}})
for doc in entry:
print(doc)

Resources