Spring-Mongo-Data Update only allows one positional argument? - spring

I have a data model with the following structure:
{
_id: ObjectId(''),
strs: [
{
_id: ObjectId(''),
nds: [
{
_id: ObjectId(''),
title: ''
},
.
.
.
]
},
.
.
.
]
}
Following query works perfectly fine in mongo shell:
mongo.update({_id: ObjectId(''), 'strs._id': ObjectId(''), 'strs.nds._id': ObjectId('')}, {$set: {'strs.$.nds.$.title': 'new-title'}})
I am trying to do the same in Spring Boot, I have written the following lines of code:
Criteria criteria = new Criteria();
criteria.addOperator(Criteria.where("id").is(id),
Criteria.where("strs.id").is(strsId), Criteria.where("strs.nds.id", ndsId));
Query query = new Query().addCriteria(criteria);
Update update = new Update();
update.set("strs.$.nds.$.title", title);
mongoTemplate.findAndModify(query, update, MyModel.class);
But, this is not working as expected. It's saying that mongo cannot create a title field inside [nds: {...}]. So, I logged the queries MongoTemplate was generating, and it turns out that MongoTemplate was removing the second positional argument $ from the query.
This was the generated query:
mongo.update({...}, {$set: {'strs.$.nds.title': 'new-title'}})
And this was the reason mongo was throwing an exception saying it cannot create a title field in an array.
Am I doing this wrong? Because MongoTemplate is generating an invalid query which is failing (obviously).

What you can do is
update.set("strs.$[elmStr].nds.$[elmNds].title", title)
.filterArray("elmStr._id", strsId)
.filterArray("elmNds._id",ndsId);
And refer positional operator

Related

How to add a json in a nested array of a mongodb document using Spring?

Document stored in mongodb:
{
"CNF_SERVICE_ID":"1",
"SERVICE_CATEGORY":"COMMON_SERVICE",
"SERVICES":[{
"SERVICE_NAME":"Authentication Service",
"VERSIONS":[{
"VERSION_NAME":"AuthenticationServiceV6_3",
"VERSION_NUMBER":"2",
"VERSION_NOTES":"test",
"RELEASE_DATE":"21-02-2020",
"OBSOLETE_DATE":"21-02-2020",
"STATUS":"Y",
"GROUPS":[{
"GROUP_NAME":"TEST GROUP",
"CREATED_DATE":"",
"NODE_NAMES":[
""
],
"CUSTOMERS":[{
"CUSTOMER_CONFIG_ID":"4",
"ACTIVATION_DATE":"21-02-2020",
"DEACTIVATION_DATE":"21-02-2020",
"STATUS":"Y"
}]
}]
}]
}
]
}
Now, I need to add another customer json to the array "CUSTOMERS" inside "GROUPS" in the same document above. The customer json would be like this:
{
"CUSTOMER_CONFIG_ID":"10",
"ACTIVATION_DATE":"16-03-2020",
"DEACTIVATION_DATE":"16-03-2021",
"STATUS":"Y"
}
I tried this:
Update update = new Update().push("SERVICES.$.VERSIONS.GROUPS.CUSTOMERS",customerdto);
mongoOperations.update(query, update, Myclass.class, "mycollection");
But, I am getting the exception: org.springframework.data.mongodb.UncategorizedMongoDbException: Command failed with error 28 (PathNotViable): 'Cannot create field 'GROUPS' in element
[ EDIT ADD ]
I was able to update it using the filtered positional operator. Below is the query I used:
update(
{ "SERVICE_CATEGORY":"COMMON_SERVICE", "SERVICES.SERVICE_NAME":"Authentication Service", "SERVICES.VERSIONS.VERSION_NAME":"AuthenticationServiceV6_3"},
{ $push:{"SERVICES.$[].VERSIONS.$[].GROUPS.$[].CUSTOMERS": { "CUSTOMER_CONFIG_ID":"6", "ACTIVATION_DATE":"31-03-2020", "STATUS":"Y" } } }
);
Actually, this query updated all the fields irrespective of the filter conditions. So. I tried this but I am facing syntax exception. Please help.
update(
{"SERVICE_CATEGORY":"COMMON_SERVICE"},
{"SERVICES.SERVICE_NAME":"Authentication Service"},
{"SERVICES.VERSIONS.VERSION_NAME":"AuthenticationServiceV6_3"}
{
$push:{"SERVICES.$[service].VERSIONS.$[version].GROUPS.$[group].CUSTOMERS":{
"CUSTOMER_CONFIG_ID":"6",
"ACTIVATION_DATE":"31-03-2020",
"STATUS":"Y"
}
}
},
{
multi: true,
arrayFilters: [ { $and:[{ "version.VERSION_NAME": "AuthenticationServiceV6_3"},{"service.SERVICE_NAME":"Authentication Service"},{"group.GROUP_NAME":"TEST GROUP"}]} ]
}
);
Update: April 1,2020
The code I tried:
validationquery.addCriteria(Criteria.where("SERVICE_CATEGORY").is(servicedto.getService_category()).and("SERVICES.SERVICE_NAME").is(servicedetail.getService_name()).and("SERVICES.VERSIONS.VERSION_NAME").is(version.getVersion_name()));
Update update=new Update().push("SERVICES.$[s].VERSIONS.$[v].GROUPS.$[].CUSTOMERS", customer).filterArray(Criteria.where("SERVICE_CATEGORY").is(servicedto.getService_category()).and("s.SERVICE_NAME").is(servicedetail.getService_name()).and("v.VERSION_NAME").is(version.getVersion_name()));
mongoOperations.updateMulti(validationquery, update, ServiceRegistrationDTO.class, collection, key,env);
The below exception is thrown:
ERROR com.sample.amt.mongoTemplate.MongoOperations - Exception in count(query, collectionName,key,env) :: org.springframework.dao.DataIntegrityViolationException: Error parsing array filter :: caused by :: Expected a single top-level field name, found 'SERVICE_CATEGORY' and 's'; nested exception is com.mongodb.MongoWriteException: Error parsing array filter :: caused by :: Expected a single top-level field name, found 'SERVICE_CATEGORY' and 's'
This update query adds the JSON to the nested array, "SERVICES.VERSIONS.GROUPS.CUSTOMERS", based upon the specified filter conditions. Note that your filter conditions direct the update operation to the specific array (of the nested arrays).
// JSON document to be added to the CUSTOMERS array
new_cust = {
"CUSTOMER_CONFIG_ID": "6",
"ACTIVATION_DATE": "31-03-2020",
"STATUS": "Y"
}
db.collection.update(
{
"SERVICE_CATEGORY": "COMMON_SERVICE",
"SERVICES.SERVICE_NAME": "Authentication Service",
"SERVICES.VERSIONS.VERSION_NAME": "AuthenticationServiceV6_3"
},
{
$push: { "SERVICES.$[s].VERSIONS.$[v].GROUPS.$[g].CUSTOMERS": new_cust }
},
{
multi: true,
arrayFilters: [
{ "s.SERVICE_NAME": "Authentication Service" },
{ "v.VERSION_NAME": "AuthenticationServiceV6_3" },
{ "g.GROUP_NAME": "TEST GROUP" }
]
}
);
Few things to note when updating documents with nested arrays of more than one level nesting.
Use the all positional operator $[] and the filtered positional
operator $[<identifier>], and not the $ positional operator.
With filtered positional operator specify the array filter conditions
using the arrayFilters parameter. Note that this will direct your update to target the specific nested array.
For the filtered positional operator $[<identifier>], the
identifier must begin with a lowercase letter and contain only
alphanumeric characters.
References:
Array Update
Operators
db.collection.update() with arrayFilters
Thanks to #prasad_ for providing the query. I was able to eventually convert the query successfully to code with Spring data MongoTemplate's updateMulti method. I have posted the code below:
Query validationquery = new Query();
validationquery.addCriteria(Criteria.where("SERVICE_CATEGORY").is(servicedto.getService_category()).and("SERVICES.SERVICE_NAME").is(servicedetail.getService_name()).and("SERVICES.VERSIONS.VERSION_NAME").is(version.getVersion_name()));
Update update=new Update().push("SERVICES.$[s].VERSIONS.$[v].GROUPS.$[].CUSTOMERS", customer).filterArray(Criteria.where("s.SERVICE_NAME").is(servicedetail.getService_name())).filterArray(Criteria.where("v.VERSION_NAME").is(version.getVersion_name()));
mongoOperations.updateMulti(validationquery, update, ServiceRegistrationDTO.class, collection, key,env);
mongoTemplateobj.updateMulti(validationquery, update, ServiceRegistrationDTO.class, collection, key,env);

What kind of Java type to pass into Criteria.all()?

I am trying to find a document with an array of tags which match a list of values,
using the MongoDB's $all function through Spring Data MongoDB API for all().
Tag is a embedded document with two fields: type and value.
I am not sure what kind of Java type to pass in to the method as it accepts an array of Objects, tried to pass in an array of Criteria objects into the the function but the output is below:
Query: { "tags" : { "$all" : [ { "$java" : org.springframework.data.mongodb.core.query.Criteria#5542c4fe }, { "$java" : org.springframework.data.mongodb.core.query.Criteria#5542c4fe } ] } }, Fields: { }, Sort: { }
How should I proceed?
I want to achieve the following:
db.template.find( { tags: { $all: [ {type:"tagOne", value: "valueOne"}, {type:"tagTwo", value: "valueTwo"} ] } } )
Edited for clarity:
The code which I used is similar to:
Query query = new Query(baseCriteria.and("tags").all( criteriaList.toArray()))
The criteria list is formed by:
Criteria criteria = new Criteria();
criteria.and("type").is(tag.getType()).and("value").is(tag.getValue());
criteriaList.add(criteria);
OK, I've just found out, the Java type required is org.bson.Document, which you can get using:
criteria.getCriteriaObject()

How do I add an OR condition in a graphql where statement?

I cannot get a WHERE statement working with an 'OR' condition in Strapi via graphql playground.
I would like to return all results where either the 'title' OR 'content' fields contain the search_text.
I have tried the following:
articles(where: {
or: [
{"title_contains" : "search_text"},
{"content_contains" : "search_text"}
]
}) {
title
content
}
but an error is returned.
ERROR: "Your filters contain a field 'or' that doesnt appear on your model definition nor it's relations.
Some statements that work (but not what I am after):
where: { "title_contains" : "sometext" }
working, but behaves as an 'AND'
where: {
"title_contains" : "search_text",
"content_contains" : "search_text"
}
As of July it's possible to do it like this
(where: { _or: [{ someField: "1" }, { someField2: "2" }] })
The workaround here is to create a custom Query and make a custom database query that matches your need.
Here is how to create a custom GraphQL query:
https://strapi.io/documentation/3.0.0-beta.x/guides/graphql.html#example-2
To access the data model, you will have to use strapi.models.article (For an Article model) and inside this variable, you will access to native Mongoose or Bookshelf function. So you will be able to query with an OR

Spring boot custom query MongoDB

I have this MongoDb query:
db.getCollection('user').find({
$and : [
{"status" : "ACTIVE"},
{"last_modified" : { $lt: new Date(), $gte: new Date(new Date().setDate(new Date().getDate()-1))}},
{"$expr": { "$ne": ["$last_modified", "$time_created"] }}
]
})
It works in Robo3T, but when I put this in spring boot as custom query, it throws error on project start.
#Query("{ $and : [ {'status' : 'ACTIVE'}, {'last_modified' : { $lt: new Date(), $gte: new Date(new Date().setDate(new Date().getDate()-1))}}, {'$expr': { '$ne': ['$last_modified', '$time_created']}}]}")
public List<User> findModifiedUsers();
I tried to make query with Criteria in spring:
Query query = new Query();
Criteria criteria = new Criteria();
criteria.andOperator(Criteria.where("status").is(UserStatus.ACTIVE), Criteria.where("last_modified").lt(new Date()).gt(lastDay), Criteria.where("time_created").ne("last_modified"));
but it doesn't work, it returns me all users like there is no this last criteria not equal last_modified and time_created.
Does anyone know what could be problem?
I think that this feature is not supported yet by Criteria - check this https://jira.spring.io/browse/DATAMONGO-1845 .
One workaround is to pass raw query via mongoTemplate like this:
BasicDBList expr = new BasicDBList();
expr.addAll(Arrays.asList("$last_modified","$time_created"));
BasicDBList and = new BasicDBList();
and.add(new BasicDBObject("status","ACTIVE"));
and.add(new BasicDBObject("last_modified",new BasicDBObject("$lt",new Date()).append("$gte",lastDate)));
and.add(new BasicDBObject("$expr",new BasicDBObject("$ne",expr)));
Document document = new Document("$and",and);
FindIterable<Document> result = mongoTemplate.getCollection("Users").find(document);

Building queries in ruflin Elastic search Symfony3

I am having hard times with Elastica and ruflin library for PHP to build the queries. There are no many examples I could follow.
I have User entity in Symfony project which consists of the following fields:
firstName, lastName, emailAddress, studio and member or client (depended on what user it is)
So now I would like to filter out some results.
I want to display users only matching the certain studio:
$finder = $this->get('fos_elastica.finder.app.user');
$boolQuery = new BoolQuery();
$fieldQuery = new Match();
$fieldQuery->setFieldQuery('studio', 'StackOverflow studio');
$boolQuery->addMust($fieldQuery);
I want to display all users with name John:
$fieldQuery = new MoreLikeThis();
$fieldQuery->setFields(['firstName', 'lastName', 'emailAddress']);
$fieldQuery->setLikeText('John');
$boolQuery->addMust($fieldQuery);
I want to get members of that studio (not clients)
Note: User has relation to Client and Member entity. Depending on that, it will show either Member:Object or Client:Object. In this case I want to be sure that Member exists.
$fieldQuery = new Filtered();
$fieldQuery->setFilter(new Exists('member'));
$boolQuery->addMust($fieldQuery);
$users = $finder->find($boolQuery);
The problem is that... it does not work. It shows me 0 results. No matter how I play with that it shows me 0 results or wrong results.
Can someone help me out how to build proper query using ruflin library for Elastica to get results based in the conditions I have mentioned above?
RAW QUERY:
{
"query":{
"bool":{
"must":[
{
"match":{
"studio":{
"query":"StackOverflow studio"
}
}
},
{
"filtered":{
"filter":{
"exists":{
"field":"member"
}
}
}
},
{
"more_like_this":{
"fields":[
"firstName",
"lastName",
"emailAddress"
],
"like_text":"John"
}
}
]
}
}
}
UPDATE 1:
I have manged to fix 3rd query (Exists('member')). It turned out that I have forgot to put client and member under mappings in config.yml. Still 2nd query fails.
Since there's no answer, I will answer myself.
Instead of using MoreLikeThis query to find a piece of text, I have used QueryString and thing started to work
Here's how whole thing looks like now:
config.yml:
fos_elastica:
...
indexes:
app:
types:
user:
mappings:
emailAddress: ~
firstName: ~
lastName: ~
studio: ~
member: ~
client: ~
persistence:
driver: orm
model: AppBundle\Entity\User
provider: ~
listener:
finder: ~
Controller:
$finder = $this->get('fos_elastica.finder.app.user');
$boolQuery = new BoolQuery();
$fieldQuery = new Match();
$fieldQuery->setFieldQuery('studio', 'StackOverflow studio');
$boolQuery->addMust($fieldQuery);
$fieldQuery = new Filtered();
$fieldQuery->setFilter(new Exists('member'));
$boolQuery->addMust($fieldQuery);
$fieldQuery = new QueryString();
$fieldQuery->setFields(['firstName', 'lastName', 'emailAddress']);
$fieldQuery->setQuery('John*'); // * wildcard will filter look a like results for you.
$boolQuery->addMust($fieldQuery);
$query = new Query();
$query->setQuery($boolQuery);
$query->addSort(['firstName' => 'asc']);
$users = $finder->find($query);

Resources