how to query case insensitivity and contains with Spring MongoTemplate - mongodb

I have JSON documents in MongoDB like these
{
"os" : "Windows 10"
}
{
"os" : "WINDOWS 8"
}
{
"os" : "UBunTu 18.04"
}
how can I query on os with case insensitivity and contains using Spring MongoTemplate so that I can pass ["windows", "ubuntu"] and get all three documents from DB
I tried something like this but did not work.
String osRegEx = String.join(" | ", new String[]{"windows", "ubuntu"});
query.addCriteria(Criteria.where("os").regex(osRegEx, "i"));

It worked in this way...
String osRegEx = String.join("|", new String[]{"windows.*", "ubuntu.*"});
query.addCriteria(Criteria.where("os").regex("("+osRegEx+")", "i"));

I think you are facing this issue because of the string that you have provided for regex. You have Windows 10 etc in mongo with the version number but you are only having the regex string to match the windows/ubuntu part :
String osRegEx = String.join(" | ", new String[]{"windows.*", "ubuntu.*"});
query.addCriteria(Criteria.where("os").regex(osRegEx, "i"));

Here is a solution:
String [] queryStrs = { "windows", "ubuntu" };
String patternStr = Stream.of(queryStrs).collect(Collectors.joining("|", "(", ")"));
Pattern pattern = Pattern.compile(patternStr, Pattern.CASE_INSENSITIVE);
Criteria criteria = new Criteria().regex(pattern);
Query query = Query.query(criteria);
List<Foo> result = mongoTemplate.find(query, Foo.class);

Related

How to compare two collections and archive documents which are not common

I have two collections for example CollectionA and CollectionB both have common filed which is hostname
Collection A :
{
"hostname": "vm01",
"id": "1",
"status": "online",
}
Collection B
{
"hostname": "vm01",
"id": "string",
"installedversion": "string",
}
{
"hostname": "vm02",
"id": "string",
"installedversion": "string",
}
what i want to achieve is when i receive a post message for collection B
I want to check if the record exists in Collection B based on hostname and update all the values. if not insert the new record ( i have read it can be achieved by using upsert -- still looking how to make it work)
I want to check if the hostname is present in Collection A , if not move the record from collection B to another collection which is collection C ( as archive records).ie in the above hostname=vm02 record from collection B should be moved to collectionC
how can i achieve this using springboot mongodb anyhelp is appreciated.The code which i have to save the Collection B is as follows which i want to update to achieve the above desired result
public RscInstalltionStatusDTO save(RscInstalltionStatusDTO rscInstalltionStatusDTO) {
log.debug("Request to save RscInstalltionStatus : {}", rscInstalltionStatusDTO);
RscInstalltionStatus rscInstalltionStatus = rscInstalltionStatusMapper.toEntity(rscInstalltionStatusDTO);
rscInstalltionStatus = rscInstalltionStatusRepository.save(rscInstalltionStatus);
return rscInstalltionStatusMapper.toDto(rscInstalltionStatus);
}
Update 1 : The below works as i expected but I think there should be a better way to do this.
public RscInstalltionStatusDTO save(RscInstalltionStatusDTO rscInstalltionStatusDTO) {
log.debug("Request to save RscInstalltionStatus : {}", rscInstalltionStatusDTO);
RscInstalltionStatus rscInstalltionStatus = rscInstalltionStatusMapper.toEntity(rscInstalltionStatusDTO);
System.out.print(rscInstalltionStatus.getHostname());
Query query = new Query(Criteria.where("hostname").is(rscInstalltionStatus.getHostname()));
Update update = new Update();
update.set("configdownload",rscInstalltionStatus.getConfigdownload());
update.set("rscpkgdownload",rscInstalltionStatus.getRscpkgdownload());
update.set("configextraction",rscInstalltionStatus.getConfigextraction());
update.set("rscpkgextraction",rscInstalltionStatus.getRscpkgextraction());
update.set("rscstartup",rscInstalltionStatus.getRscstartup());
update.set("installedversion",rscInstalltionStatus.getInstalledversion());
mongoTemplate.upsert(query, update,RscInstalltionStatus.class);
rscInstalltionStatus = rscInstalltionStatusRepository.findByHostname(rscInstalltionStatus.getHostname());
return rscInstalltionStatusMapper.toDto(rscInstalltionStatus);
}
Update2 : with the below code i am able to get move the records to another collection
String query = "{$lookup:{ from: \"vmdetails\",let: {rschostname: \"$hostname\"},pipeline:[{$match:{$expr:{$ne :[\"$hostname\",\"$$rschostname\"]}}}],as: \"rscInstall\"}},{$unwind:\"$rscInstall\"},{$project:{\"_id\":0,\"rscInstall\":0}}";
AggregationOperation rscInstalltionStatusTypedAggregation = new CustomProjectAggregationOperation(query);
LookupOperation lookupOperation = LookupOperation.newLookup().from("vmdetails").localField("hostname").foreignField("hostname").as("rscInstall");
UnwindOperation unwindOperation = Aggregation.unwind("$rscInstall");
ProjectionOperation projectionOperation = Aggregation.project("_id","rscInstall").andExclude("_id","rscInstall");
OutOperation outOperation = Aggregation.out("RscInstallArchive");
Aggregation aggregation = Aggregation.newAggregation(rscInstalltionStatusTypedAggregation,unwindOperation,projectionOperation,outOperation);
List<BasicDBObject> results = mongoTemplate.aggregate(aggregation,"rsc_installtion_status",BasicDBObject.class).getMappedResults();
this issue which i have here is it returns multiple records
Found the solution , there may be other best solutions but for me this one worked
create a class customeAggregationGeneration (found in SO answers and extended to match my needs)
public class CustomProjectAggregationOperation implements AggregationOperation {
private String jsonOperation;
public CustomProjectAggregationOperation(String jsonOperation) {
this.jsonOperation = jsonOperation;
}
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return aggregationOperationContext.getMappedObject(Document.parse(jsonOperation));
}
}
String lookupquery = "{$lookup :{from:\"vmdetails\",localField:\"hostname\",foreignField:\"hostname\"as:\"rscinstall\"}}";
String matchquery = "{ $match: { \"rscinstall\": { $eq: [] } }}";
String projectquery = "{$project:{\"rscinstall\":0}}";
AggregationOperation lookupOpertaion = new CustomProjectAggregationOperation(lookupquery);
AggregationOperation matchOperation = new CustomProjectAggregationOperation(matchquery);
AggregationOperation projectOperation = new CustomProjectAggregationOperation(projectquery);
Aggregation aggregation = Aggregation.newAggregation(lookupOpertaion, matchOperation, projectOperation);
ArrayList<Document> results1 = (ArrayList<Document>) mongoTemplate.aggregate(aggregation, "rsc_installtion_status", Document.class).getRawResults().get("result");
// System.out.println(results1);
for (Document doc : results1) {
// System.out.print(doc.get("_id").toString());
mongoTemplate.insert(doc, "RscInstallArchive");
delete(doc.get("_id").toString());

Access item in ListField/EmbeddedDocumentListField by using __raw__ in MongoEngine [duplicate]

I use mongoengine with Django and python.
This is my code:
class Chambre(EmbeddedDocument):
max_personne = IntField(default=0)
prix = IntField(default=0)
class Hotel(Document):
code = IntField(default=0)
nom = StringField(max_length=200)
chambre = ListField(EmbeddedDocumentField(Chambre))
resume = StringField(max_length=200)
1 - I want a query to filter all Hotel that have at least a Chambre with prix >= a (a floeat number)
2 - also have that Chambre
Any idea?
You can use the embedded notation as well as the Query Operator for "greater than or equal "
Hotel.objects(chambre__prix__gte=a)
Or if you need to cast as an integer:
Hotel.objects(chambre__prix__gte=int(math.floor(a)))
If you want to only project the "matched" element, use a raw query directly on the driver instead:
Hotel._get_collection().find(
{ 'chambre.prix': { '$gte': int(math.floor(a)) } },
{ 'chambre.$': 1 }
)

Grails 3 saving mongoDB document with both _id and id

We noticed an issue after an upgrade to grails 3 that we were saving mongoDB documents with both _id and id. (example document below)
How do we stop the saving of id? This happens for every collection the application creates and updates documents for.
{
"_id" : ObjectId("5b0ed1b710b3641a98aaee63"),
"value" : "testing",
"type" : "testingCreate",
"updateDate" : ISODate("2018-05-30T16:30:39.987Z"),
"updateUser" : "TSTUSR",
"id" : ObjectId("5b0ed1b710b3641a98aaee63")
}
The save is being called from the following
def test = new AppParam(type: "testingCreate",
updateUser: "TSTUSR",
updateDate: new Date(),
value: "testing")
test.save(failOnError:true, flush:true)
for the appParam domain of
class AppParam {
ObjectId id
String type
String value
String updateUser
Date updateDate
static mapWith = "mongo"
static mapping = {
version false
writeConcern WriteConcern.ACKNOWLEDGED
}
static constraints = {
type size: 1..50, matches:/^[^<>]{1,50}$/, validator: { field, obj ->
if (!field.trim()) return ['typeRequired']
return true
}
value size: 1..2000, matches:/^[^<>]{1,2000}$/, validator: { field, obj ->
if (!field.trim()) return ['valueRequired']
return true
}
}
}
We are using grailsVersion 3.2.11 and gormVersion 6.1.7.RELEASE
Try the following in the mapping closure.
static mapping {
id column: '_id'
version false
writeConcern WriteConcern.ACKNOWLEDGED
}
Did a bit more research into Mike W's comment after Grails 3.X it should be defaulting the mongodb engine to codec and we were manually defaulting the mongodb.engine = "mapping".

MongoDB Java API Query UPDATE include word

I have a problem. I need include one word in the value from the field: "name".
For example:
{name : "Apple inc."}
I would like add the word "Company".
{name : "Company Apple inc."}
But, I can't. Where is it my issue?
collectionCOMPANIES.updateMany(
new Document("$where", "true")
,
new Document("$set", new Document("name","Company + $name")
));
If I do this, return:
{name : "Company + $name"}
Thanks.
******* UPDATE SOLUTION******
For the final code In java this is:
collectionCOMPANIES.find().forEach(new Block<Document>() {
public void apply(final Document document) {
final Document DocuNew = new Document();
DocuNew.putAll(document);
DocuNew.put("name", "Company " + document.get("name"));
newcollection.insertOne(DocuNew);
}
});
Try this.
db.collectionCOMPANIES.find().snapshot().forEach( function (doc) {
doc.name = "Company " + doc.name;
db.collectionCOMPANIES.save(doc);
});
For this I am using snapshot provided by mongodb. you can read more about it here

Phrase query in Lucene 6.2.0

I have a document like this:
{
"_id" : ObjectId("586b723b4b9a835db416fa26"),
"name" : "test",
"countries" : {
"country" : [
{
"name" : "russia iraq"
},
{
"name" : "USA china"
}
]
}
}
In MongoDB I am trying to retrieve it using phrase query(Lucene 6.2.0). My code looks as folllows:
StandardAnalyzer analyzer = new StandardAnalyzer();
// 1. create the index
Directory index = new RAMDirectory();
IndexWriterConfig config = new IndexWriterConfig(analyzer);
try {
IndexWriter w = new IndexWriter(index, config);
MongoClient client = new MongoClient("localhost", 27017);
DB database = client.getDB("test123");
DBCollection coll = database.getCollection("test1");
//MongoCollection<org.bson.Document> collection = database.getCollection("test1");
DBCursor cursor = coll.find();
System.out.println(cursor);
while (cursor.hasNext()) {
BasicDBObject obj = (BasicDBObject) cursor.next();
Document doc = new Document();
BasicDBObject f = (BasicDBObject) (obj.get("countries"));
List<BasicDBObject> dts = (List<BasicDBObject>)(f.get("country"));
doc.add(new TextField("id",obj.get("_id").toString().toLowerCase(), Field.Store.YES));
doc.add(new StringField("name",obj.get("name").toString(), Field.Store.YES));
doc.add(new StringField("countries",f.toString(), Field.Store.YES));
for(BasicDBObject d : dts){
doc.add(new StringField("country",d.get("name").toString(), Field.Store.YES));
//
}
w.addDocument(doc);
}
w.close();
and my search goes like :
PhraseQuery query = new PhraseQuery("country", "iraq russia" );
// 3. search
int hitsPerPage = 10;
IndexReader reader = DirectoryReader.open(index);
IndexSearcher searcher = new IndexSearcher(reader);
TopDocs docs = searcher.search(query, hitsPerPage);
ScoreDoc[] hits = docs.scoreDocs;
// 4. display results
System.out.println("Found " + hits.length + " hits.");
for(int j=0;j<hits.length;++j) {
int docId = hits[j].doc;
Document d = searcher.doc(docId);
System.out.println(d);
}
reader.close();
}
catch (Exception e) {
e.printStackTrace();
}
I am getting zero hits for this query. Can anyone tell what I am doing wrong?
jars used:
lucene-queries4.2.0
lucene-queryparser-6.2.1
lucene-analyzers-common-6.2.0
i made certain changes which goes like:
Query query = new PhraseQuery.Builder()
.add(new Term("country", "iraq"))
.add(new Term("country", "russia"))
.setSlop(2)
.build();
and also i changed the type of feild while indexing :
for(BasicDBObject d : dts){
doc.add(newTextField("country",d.get("name").toString(), Field.Store.YES));
}
But can anyone tell me the difference between StringFeild and TextFeild while indexing?
Firstly, never mix Lucene versions. All your jars should be the same version. Upgrade lucene-queries to 6.2.1. In practice you might or might not run into trouble mixing up 6.2.0 and 6.2.1, but you definitely should upgrade lucene-analyzers-common as well.
PhraseQuery doesn't analyze for you, you have to add terms to it separately. In your example, "iraq russia" is treated as a single terms, rather than two separate (analyzed) terms.
It should look something like this:
Query query = new PhraseQuery.Builder()
.add(new Term("country", "iraq"))
.add(new Term("country", "russia"))
.build();
If you want something that will analyze for you, you can use the QueryParser:
QueryParser parser = new QueryParser("country", new StandardAnalyzer())
Query query = queryparser.parse("\"iraq russia\"");