Spring MongoDB query with or operator and text search - spring

How can i build this MongoDB query with Spring Criteria?
{
$or: [
{ "$text" : { "$search" : "570-11024" } },
{"productDetails.code": "572-R110"}
]
}
It combines a fulltext index search with normal Where criteria with an orOperator.
Query's orOperator(Criteria... criteria) method takes only Criteria and no TextCriteria and also no CriteriaDefinition interface.

Yeah you are right, in spring data mongo you could do this,
final TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matchingAny("570-11024");
final DBObject tc = textCriteria.getCriteriaObject();
final Criteria criteria = Criteria.where("productDetails.code").is("572-R110");
final DBObject co = criteria.getCriteriaObject();
BasicDBList or = new BasicDBList();
or.add(tc);
or.add(co);
DBObject qq = new BasicDBObject("$or", or);
// Use MongoTemplate to execute command
mongoTemplate.executeCommand(qq);

Yes, you currently cannot use the Query's orOperator method to combine Criteria and TextCriteria. A workaround involves converting both the Criteria and TextCriteria objects to its Document representations, adding it to a BasicDbList and then converting back to a "$or" Criteria object.
TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matchingAny("570-11024");
Criteria criteria = Criteria.where("productDetails.code").is("572-R110");
BasicDBList bsonList = new BasicDBList();
bsonList.add(criteria.getCriteriaObject());
bsonList.add(textCriteria.getCriteriaObject());
Query query = new Query();
query.addCriteria(new Criteria("$or").is(bsonList));
mongoTemplate.find(query, YourEntity.class);
PS: Someone has raised this issue in the spring-data-mongodb repo with a proposed fix by
changing the parameter types of orOperator from Criteria to CriteriaDefinition.
https://github.com/spring-projects/spring-data-mongodb/issues/3895.

Related

What is replacement for FullTextQuery.setCriteriaQuery() in Hibernate Search 6?

I am migrating Hibernate Search 5 to Hibernate Search 6.
Though, the documentation is really helpful, I am not able to find alternative for criteria query in Hibernate Search 6 and didn't quite get from documentation.
This is the Hibernate Search 5 query that I am trying to convert,
final Criteria criteria = entityManager.unwrap(Session.class).createCriteria(KnowledgeData.class);
criteria.add(Restrictions.eq("deleted", knowledgeSearchRequest.isDeleted()));
if (knowledgeSearchRequest.isPublished()) {
criteria.add(Restrictions.eq("published", knowledgeSearchRequest.isPublished()));
}
if (!allDesk) {
criteria.add(Restrictions.eq("deskId", deskId));
knowledgeSearchRequest.setDesk(deskId);
} else {
Disjunction orJunction = Restrictions.disjunction();
for (String desk : knowledgeSearchRequest.getDeskIds()) {
orJunction.add(Restrictions.eq("deskId", desk));
}
criteria.add(orJunction);
}
if (knowledgeSearchRequest.getLang() != null && knowledgeSearchRequest.getLang().size() > 0) {
criteria.createAlias("language", "lan");
Disjunction disJunction = Restrictions.disjunction();
for (String lang : knowledgeSearchRequest.getLang()) {
disJunction.add(Restrictions.eq("lan.elements", lang));
}
criteria.add(disJunction);
}
if (knowledgeSearchRequest.getTags() != null && knowledgeSearchRequest.getTags().size() > 0) {
criteria.createAlias("tags", "tag");
Disjunction disJunction = Restrictions.disjunction();
for (String tag : knowledgeSearchRequest.getTags()) {
disJunction.add(Restrictions.eq("tag.elements", tag));
}
criteria.add(disJunction);
}
criteria.add(Restrictions.ne("dataType", DataType.FOLDER));
// if (userProvider.getCurrentUser().isSystemUser() || visibleToUser) {
final List<DataVisibility> visibility = new ArrayList<>();
visibility.add(DataVisibility.PUBLIC);
if (knowledgeSearchRequest.isAddCpUserDocs()) {
visibility.add(DataVisibility.ALL_USERS_OF_CUSTOMER_PORTAL_ONLY);
}
if (knowledgeSearchRequest.isIncludeCpDocs()) {
visibility.add(DataVisibility.CUSTOMER_PORTAL);
visibility.add(DataVisibility.ALL_SIGNED_IN_USERS_OF_CUSTOMER_PORTAL_ONLY);
visibility.add(DataVisibility.ALL_USERS_OF_CUSTOMER_PORTAL_ONLY);
}
criteria.add(Restrictions.in("visibility", visibility));
// }
if (knowledgeSearchRequest.isPublished()) {
final long now = System.currentTimeMillis();
criteria.add(Restrictions.or(
Restrictions.and(Restrictions.isNotNull("validFrom"), Restrictions.lt("validFrom", now)),
Restrictions.isNull("validFrom")));
criteria.add(Restrictions.or(
Restrictions.and(Restrictions.isNotNull("validTo"), Restrictions.gt("validTo", now)),
Restrictions.isNull("validTo")));
}
And, the predicate that i have built so far is,
searchPredicateFactory.bool(
f -> f.should(searchPredicateFactory.phrase().field(KnowledgeData.STANDARD_FIELD_NAME_NAME).boost(3)
.field(KnowledgeData.STANDARD_FIELD_NAME_DISPLAY_NAME).boost(3)
.field("description").boost(2).field("content").matching(resultantQuery))
.should(searchPredicateFactory.wildcard().field(KnowledgeData.STANDARD_FIELD_NAME_NAME).boost(3)
.field(KnowledgeData.STANDARD_FIELD_NAME_DISPLAY_NAME).boost(3)
.field("description").boost(2).field("content").matching(resultantQuery))).toPredicate();
Any leads are appreciated.
This is the Hibernate Search 5 query that I am trying to convert,
I'll nitpick a bit: this is not a Hibernate Search 5 query, this is a Hibernate (ORM) Criteria query. Those restrictions are executed against the database, not against the search indexes.
From the title of your question, I'll assume you are adding those restrictions to your Hibernate Search query using FullTextQuery.setCriteriaQuery(). Be aware that the documentation in Hibernate Search 5 states "using restriction (ie a where clause) on your Criteria query should be avoided" and the javadoc goes even further by stating "No where restriction can be defined".
Regardless... it seems it used to work in Hibernate Search 5, at least in some cases.
Now, to migrate this to Hibernate Search 6+, there is a detailed migration guide, with a section specifically about your problem:
Hibernate Search 6 does not allow adding a Criteria object to a search query.
[...]
If your goal is to apply a filter expressed by an SQL "where" clause executed in-database, rework your query to project on the entity ID, and execute a JPA/Hibernate ORM query after the search query to filter the entities and load them.
So in short, do something like this:
List<Long> ids = Search.session(entityManager).search(MyEntity.class)
.select(f -> f.id(Long.class))
.where(f -> ...)
.fetchHits(20);
criteria.add(Restrictions.in("id", ids));
List<MyEntity> hits = criteria.list();
Note this is only a quick fix: just like setCriteria in Hibernate Search 5, this can perform very badly, plays very badly with pagination, and can result in incorrect hit counts.
I would recommend indexing the properties you use in your Criteria query, and defining your whole query using Hibernate Search only, so as to avoid running the query once against Elasticsearch and then once again against your database.
See also https://hibernate.atlassian.net/browse/HSEARCH-3630

Spring Data elastic search with out entity fields

I'm using spring data elastic search, Now my document do not have any static fields, and it is accumulated data per qtr, I will be getting ~6GB/qtr (we call them as versions). Lets say we get 5GB of data in Jan 2021 with 140 columns, in the next version I may get 130 / 120 columns, which we do not know, The end user requirement is to get the information from the database and show it in a tabular format, and he can filter the data. In MongoDB we have BasicDBObject, do we have anything in springboot elasticsearch
I can provide, let say 4-5 columns which are common in every version record and apart from that, I need to retrieve the data without mentioning the column names in the pojo, and I need to use filters on them just like I can do in MongoDB
List<BaseClass> getMultiSearch(#RequestBody Map<String, Object>[] attributes) {
Query orQuery = new Query();
Criteria orCriteria = new Criteria();
List<Criteria> orExpression = new ArrayList<>();
for (Map<String, Object> accounts : attributes) {
Criteria expression = new Criteria();
accounts.forEach((key, value) -> expression.and(key).is(value));
orExpression.add(expression);
}
orQuery.addCriteria(orCriteria.orOperator(orExpression.toArray(new Criteria[orExpression.size()])));
return mongoOperations.find(orQuery, BaseClass.class);
}
You can define an entity class for example like this:
public class GenericEntity extends LinkedHashMap<String, Object> {
}
To have that returned in your calling site:
public SearchHits<GenericEntity> allGeneric() {
var criteria = Criteria.where("fieldname").is("value");
Query query = new CriteriaQuery(criteria);
return operations.search(query, GenericEntity.class, IndexCoordinates.of("indexname"));
}
But notice: when writing data into Elasticsearch, the mapping for new fields/properties in that index will be dynamically updated. And there is a limit as to how man entries a mapping can have (https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-settings-limit.html). So take care not to run into that limit.

ES Match query analogue in Lucene

I use queries like this one to run in ES:
boolQuery.must(QueryBuilders.matchQuery("field", value).minimumShouldMatch("50%"))
What's the straight analogue for this query in Lucene?
Match Query, as I understand it, basically analyzes the query, and creates a BooleanQuery out of all the terms the analyzer finds. You could get sorta close by just passing the text through QueryParser.
But you could replicate it something like this:
public static Query makeMatchQuery (String fieldname, String value) throws IOException {
//get a builder to start adding clauses to.
BooleanQuery.Builder qbuilder = new BooleanQuery.Builder();
//We need to analyze that value, and get a tokenstream to read terms from
Analyzer analyzer = new StandardAnalyzer();
TokenStream stream = analyzer.tokenStream(fieldname, new StringReader(value));
stream.reset();
//Iterate the token stream, and add them all to our query
int countTerms = 0;
while(stream.incrementToken()) {
countTerms++;
Query termQuery = new TermQuery(new Term(
fieldname,
stream.getAttribute(CharTermAttribute.class).toString()));
qbuilder.add(termQuery, BooleanClause.Occur.SHOULD);
}
stream.close();
analyzer.close();
//The min should match is a count of clauses, not a percentage. So for 50%, count/2
qbuilder.setMinimumNumberShouldMatch(countTerms / 2);
Query finalQuery = qbuilder.build();
return finalQuery;
}

Elasticsearch simultaneously search by two documents

I have two different documents in the Elasticsearch - Decision and Nomination.
Right now I can only search all of the documents wit Decision type.
I use Spring Data Elasticsearch for this purpose:
PageRequest pageRequest = DecisionUtils.createPageRequest(pageNumber, pageSize);
MultiMatchQueryBuilder fuzzyMmQueryBuilder = multiMatchQuery(query, "name", "description").fuzziness("AUTO");
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().should(fuzzyMmQueryBuilder);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withIndices(ESDecision.INDEX_NAME).withTypes(ESDecision.TYPE).withPageable(pageRequest);
NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.withQuery(boolQueryBuilder).withPageable(pageRequest).build();
return elasticsearchTemplate.queryForPage(nativeSearchQuery, ESDecision.class);
Is it possible to update this code to search by Decision and Nomination simultaneously in order to get the search result from both of them ? If the answer is yes - please show an example how to implement this and also please show how to determine at search results who is Decision and who is Nomination ? Is there any classification field that can be added into search result entity for this purpose ?

saving & updating full json document with Spring data MongoTemplate

I'm using Spring data MongoTemplate to manage mongo operations. I'm trying to save & update json full documents (using String.class in java).
Example:
String content = "{MyId": "1","code":"UG","variables":[1,2,3,4,5]}";
String updatedContent = "{MyId": "1","code":"XX","variables":[6,7,8,9,10]}";
I know that I can update code & variables independently using:
Query query = new Query(where("MyId").is("1"));
Update update1 = new Update().set("code", "XX");
getMongoTemplate().upsert(query, update1, collectionId);
Update update2 = new Update().set("variables", "[6,7,8,9,10]");
getMongoTemplate().upsert(query, update2, collectionId);
But due to our application architecture, it could be more useful for us to directly replace the full object. As I know:
getMongoTemplate().save(content,collectionId)
getMongoTemplate().save(updatedContent,collectionId)
implements saveOrUpdate functionality, but this creates two objects, do not update anything.
I'm missing something? Any approach? Thanks
You can use Following Code :
Query query = new Query();
query.addCriteria(Criteria.where("MyId").is("1"));
Update update = new Update();
Iterator<String> iterator = json.keys();
while(iterator.hasNext()) {
String key = iterator.next();
if(!key.equals("MyId")) {
Object value = json.get(key);
update.set(key, value);
}
}
mongoTemplate.updateFirst(query, update, entityClass);
There may be some other way to get keyset from json, you can use according to your convenience.
You can use BasicDbObject to get keyset.
you can get BasicDbObject using mongoTemplate.getConverter().

Resources