Return documents based on field bitwise AND operator - mongodb

I have a model which has a field 'permissions', and I would like to retrieve all the documents from my table whose field 'permissions' meets a certain condition based on a bitwise AND operator. For example, what I am doing currently is the following in my typescript codebase:
let users: User[] = await this.userModel.find();
users = users.filter( // I would like to get rid of this filter (do the filtering directly through mongo)
(user) => user.permissions & 2 > 0 // bitwise AND operator
);
However, I would like to do the operation directly through mongo, so I don't need to get all the users inside my table and then filter them out from my typescript code.

Related

Flutter Firestore only return user overview ListTile when field contains specific words

I am listing users in a CustomScrollView/SliversList,ListTiles. I have a String field in my firestore and only want to return ListTile of a user, where his String field contains specific words (more than 2). For example, the users fields contain: "Apples, Ice, Bananas, Soup, Peaches, e.g...." and i want to list all users which have apples and bananas inside the field. how can i achieve this?
The only way to do it at the moment (with the way you have it set up) is actually pulling the value and doing a string "contains" or splitting the string into an array and check whether the value is within that array, otherwise I'd advise to refactor that field and make it into an array, that way you can perform a native arrayContainsAny against your field.
For you it will look like this (with your current implementation):
// ... after pulling all users' documents
// let's say your field is called 'foodField':
var criteria = 'Banana';
var fieldContent = doc.data()['foodField'];
// you can either do this:
if (fieldContent.toLowerCase().contains(criteria.toLowerCase())) {
// ...
}
// or you can tokenize it depending on your purposes...
var foodTokens = fieldContent.split(',').map((f) => f.toLowerCase());
if (foodTokens.contains(criteria.toLowerCase()) {
// ...
}
If your Firestore field was an array type, then you could've just done that, while querying:
FirebaseFirestore.instance.collection('users').where('foodField', arrayContainsAny: ['Banana', 'Apples'])
Which then would give you only the users whose foodField contain that value.
As you can see from previous questions on querying where text contains a substring, Firestore does not currently support such text searches. The typical solutions are to either perform part of your filtering in your application code as Roman answered, or to integrate a third-party full-text search solution.
In your specific case though, your string seems to be a list of words, so I'd recommend considering to change your data model to an array of the individual values in there:
"foodFields": ["Apples", "Ice", "Banana", "Soup", "Peaches"]
You can then use array field operators in the query.
While there is no array-contains-all operator, using array-contains you can at least filter on one value in the database, and with array-contains-any you can do on OR like condition.
Another data model would be to store the individual values in a map field with value true for each of them:
"foodFields": {
"Apples": true,
"Ice": true,
"Banana": true,
"Soup": true,
"Peaches": true
}
With such a structure you can perform an AND like query with:
collectionRef
.where('foodFields.Apples', isEqualTo: true)
.where('foodFields.Bananas', isEqualTo: true)

mgo $all query an array with an array and be case insensitive?

I have an array of ingredient names that is dynamic and provided per user. I'd like to match it to mongo documents where there is an array of objects called ingredients which has a property name. I've written a query (see below) which will take query parameters from the URL and will return all the documents that have all matching ingredient names, however this search is case sensitive and I'd like it not to be.
I've considered using bson.RegEx with Option: "i", however I'm not sure how to form this query or apply it to an array of strings.
Here is the case sensitive query:
// Check for ingredients, return all recipes that can be made using supplied ingredients
if qryPrms["ingredients"] != nil {
mongodQ["ingredients.name"] = bson.M{"$all": qryPrms["ingredients"]}
}
mongodQ is the bson.M I use to query the collection later in the code. Ideally I could apply RegEx to each element in qryPrms["ingredients"] so it would return closely matching ingredients like cheese would return swiss cheese as well. This is also a more general mongodb question I suppose when it comes to querying with a dynamic array.
I was able to accomplish this using a for loop to build a slice of type bson.RegEx.
if qryPrms["ingredients"] != nil {
var ingRegEx []bson.RegEx
for i := range qryPrms["ingredients"] {
ingRegEx = append(ingRegEx, bson.RegEx{Pattern: qryPrms["ingredients"][i], Options: "i"})
}
mongodQ["ingredients.name"] = bson.M{"$all": ingRegEx}
}

How can I upsert a record and array element at the same time?

That is meant to be read as a dual upsert operation, upsert the document then the array element.
So MongoDB is a denormalized store for me (we're event sourced) and one of the things I'm trying to deal with is the concurrent nature of that. The problem is this:
Events can come in out of order, so each update to the database need to be an upsert.
I need to be able to not only upsert the parent document but an element in an array property of that document.
For example:
If the document doesn't exist, create it. All events in this stream have the document's ID but only part of the information depending on the event.
If the document does exist, then update it. This is the easy part. The update command is just written as UpdateOneAsync and as an upsert.
If the event is actually to update a list, then that list element needs to be upserted. So if the document doesn't exist, it needs to be created and the list item will be upserted (resulting in an insert); if the document does exist, then we need to find the element and update it as an upsert, so if the element exists then it is updated otherwise it is inserted.
If at all possible, having it execute as a single atomic operation would be ideal, but if it can only be done in multiple steps, then so be it. I'm getting a number of mixed examples on the net due to the large change in the 2.x driver. Not sure what I'm looking for beyond the UpdateOneAsync. Currently using 2.4.x. Explained examples would be appreciated. TIA
Note:
Reiterating that this is a question regarding the MongoDB C# driver 2.4.x
Took some tinkering, but I got it.
var notificationData = new NotificationData
{
ReferenceId = e.ReferenceId,
NotificationId = e.NotificationId,
DeliveredDateUtc = e.SentDate.DateTime
};
var matchDocument = Builders<SurveyData>.Filter.Eq(s => s.SurveyId, e.EntityId);
// first upsert the document to make sure that you have a collection to write to
var surveyUpsert = new UpdateOneModel<SurveyData>(
matchDocument,
Builders<SurveyData>.Update
.SetOnInsert(f => f.SurveyId, e.EntityId)
.SetOnInsert(f => f.Notifications, new List<NotificationData>())){ IsUpsert = true};
// then push a new element if none of the existing elements match
var noMatchReferenceId = Builders<SurveyData>.Filter
.Not(Builders<SurveyData>.Filter.ElemMatch(s => s.Notifications, n => n.ReferenceId.Equals(e.ReferenceId)));
var insertNewNotification = new UpdateOneModel<SurveyData>(
matchDocument & noMatchReferenceId,
Builders<SurveyData>.Update
.Push(s => s.Notifications, notificationData));
// then update the element that does match the reference ID (if any)
var matchReferenceId = Builders<SurveyData>.Filter
.ElemMatch(s => s.Notifications, Builders<NotificationData>.Filter.Eq(n => n.ReferenceId, notificationData.ReferenceId));
var updateExistingNotification = new UpdateOneModel<SurveyData>(
matchDocument & matchReferenceId,
Builders<SurveyData>.Update
// apparently the mongo C# driver will convert any negative index into an index symbol ('$')
.Set(s => s.Notifications[-1].NotificationId, e.NotificationId)
.Set(s => s.Notifications[-1].DeliveredDateUtc, notificationData.DeliveredDateUtc));
// execute these as a batch and in order
var result = await _surveyRepository.DatabaseCollection
.BulkWriteAsync(
new []{ surveyUpsert, insertNewNotification, updateExistingNotification },
new BulkWriteOptions { IsOrdered = true })
.ConfigureAwait(false);
The post linked as being a dupe was absolutely helpful, but it was not the answer. There were a few things that needed to be discovered.
The 'second statement' in the linked example didn't work
correctly, at least when translated literally. To get it to work, I had to match on the
element and then invert the logic by wrapping it in the Not() filter.
In order to use 'this index' on the match, you have to use a
negative index on the array. As it turns out, the C# driver will
convert any negative index to the '$' character when the query is
rendered.
In order to ensure they are run in order, you must include bulk write
options with IsOrdered set to true.

Meteor: return subset of attributes from Mongo

Im querying Mongo to get the user item, but I only want to pass through a subset of the info to the template. My current solution is this:
var returnUsers = [];
var users = Meteor.users.find().fetch();
for (var i = 0; i < users.length; i++) {
returnUsers.push(users[i].profile);
}
console.log(returnUsers);
return returnUsers;
But I'm losing the iterator. Ideally I want to just return the profile object of each user. How do you do that?
There is little point in doing this on the client. Returning a cursor with fields you don't end up using from minimongo is normally just as fast or faster than filtering fields out in javascript.
Especially for the Users collection you want to filter out the extra fields in your publication from the server. For example:
Meteor.publish('allUsers',function(){
return Meteor.users.find({},{ fields: { profile: 1 }});
});
This will publish the profile data and the _id for each user. Then when you do
Meteor.users.find({});
on the client you will only get the profile data and _id without any need to do extra filtering.
Note that the fields option only allows you to define a set of fields to include or exclude together. You cannot mix include and exclude:
{ fields: { key1: 0, key2: 1 }}
will fail.
There is no security benefit to filtering fields on the client either. The user has full access to the published collection from the console.
Seeing as you want to keep cursor as per comment in previous answer remove the fetch as this turns it into an array not a cursor and add fields like below
return Meteor.users.find({},{fields:{profile:1}});
This won't give you only profile but will also return the id as this is always sent regardless of the fields specified to return.
use `map`
var profiles=Meteor.users.find().map(function(a){return a.profile})

How to pass variable between two queries in MongoDB?

I want to put the query result from one collection in a variable and use it as input for query in another collection. The queries look like this as follows:
Query 1:
var ID=db.User.findOne({Name:"Ivan"}, {ID: 1});
db.Artists.find({"Listeners.ID":ID});
Query 2:
var Friends=db.Users.find({Friends:x});
//Users.Friends is an array of interger identifier for User
db. Artists.find({"Listeners.ID":{$in:Friends}});
But they all don't work. How to write the right one?
The query db.User.findOne({Name:"Ivan"}, {ID: 1}); does not return a single value, it returns the document, reduced to the field you requested. What you get is an object, with two fields: _id (because you didn't explicitly exclude it) and ID (when it exists in the document). Your var ID looks like this:
{
_id:ObjectId(<long hex string>),
ID:<value>
}
So when you want to query by the ID value, you need to specify it:
db.Artists.find({"Listeners.ID":ID.ID});
Regarding your second query: when you use find instead of findOne you get a cursor object which can then be used to retrieve the individual documents using cursor.next() or cursor.toArray().