Ignite TextQueries on multiple fields - lucene

I have the following object:
#Data
#AllArgsConstructor
public class SearchItem implements Serializable {
private String id;
#QueryTextField
private String description;
#QueryTextField
private String brand;
}
As you can see I have two fileds enabled with the lucene index. Im confused now how to create the TextQuery to search only for a specific field or for both fields. I want to be able to make text searches in the brand field or the descritption field or in both. There is an ignite example showing how to create a TextQuery
private static void textQuery() {
IgniteCache<Long, Person> cache = Ignition.ignite().cache(PERSON_CACHE);
// Query for all people with "Master Degree" in their resumes.
QueryCursor<Cache.Entry<Long, Person>> masters =
cache.query(new TextQuery<Long, Person>(Person.class, "Master"));
// Query for all people with "Bachelor Degree" in their resumes.
QueryCursor<Cache.Entry<Long, Person>> bachelors =
cache.query(new TextQuery<Long, Person>(Person.class, "Bachelor"));
print("Following people have 'Master Degree' in their resumes: ", masters.getAll());
print("Following people have 'Bachelor Degree' in their resumes: ", bachelors.getAll());
}
But this example is only showing that I have to pass in the class and the search string. How can I define for which field of the class the search should be performed, when I have more then one field annotated with #QueryTextField?
Example search string:
description = ?, brand = ?

You can use query syntax from Apache Lucene: https://lucene.apache.org/core/2_9_4/queryparsersyntax.html
If it does not work with your field names, I would recommend using names in uppercase instead.

Related

Apache Ignite : Ignite Repository query with "IN" clause, returns no records

I am using Apache Ignite as the back-end data store in a SpringBoot Application.
I have a requirement where I need to get all the entities whose name matches one of the names from a set of names.
Hence i am trying to get it implemented using a #Query configuration and a method named findAllByName(Iterable<String> names)as below:
Here on the Query, I am trying to use the 'IN' clause and want to pass an array of names as an input to the 'IN' clause.
#RepositoryConfig(cacheName = "Category")
public interface CategoryRepository extends IgniteRepository<Category, Long>
{
List<Category> findByName(String name);
#Query("SELECT * FROM Category WHERE name IN ( ? )")
Iterable<Category> findAllByName(Iterable<String> names); // this method always returns empty list .
}
In this the method findAllByName always returns empty list, even when ignite has Categories for which the name field matches the data passed in the query.
I am unable to figure out if there is a problem with the Syntax or the query of the method signature or the parameters.
Please try using String[] names instead for supplying parameters.
UPDATE: I have just checked the source, and we don't have tests for such scenario. It means that you're on uncharted territory even if it is somehow possible to get to work.
Otherwise looks unsupported currently.
I know your question is more specific to Spring Data Ignite feature. However, as an alternate, you can achieve it using the SqlQuery abstraction of Ignite.
You will form your query like this. I have pasted the sample below with custom sql function inSet that you will write. Also, the below tells how this is used in your sql.
IgniteCache<String, MyRecord> cache = this.ignite
.cache(this.environment.getProperty(Cache.CACHE_NAME));
String sql = "from “my-ignite-cache”.MyRecord WHERE
MyRecord.city=? AND inSet(?, MyRecord.flight)"
SqlQuery<String, MyRecord> sqlQuery = new SqlQuery<>(MyRecord.class,
sql);
sqlQuery.setArgs(MyCity, [Flight1, Flight2 ] );
QueryCursor<Entry<String, MyRecord>> resultCursor = cache.query(sqlQuery);
You can iterate the result cursor to do something meaningful from the extracted data.
resultCursor.forEach(e -> {
MyRecord record = e.getValue();
// do something with result
});
Below is the Ignite Custom Sql function which is used in the above Query - this will help in replicating the IN clause feature.
#QuerySqlFunction
public static boolean inSet(List<String> filterParamArgIds, String id) {
return filterParamArgIds.contains(id);
}
And finally, as a reference MyRecord referred above can be defined something like this.
public class MyRecord implements Serializable {
#QuerySqlField(name = "city", index = true)
private String city;
#QuerySqlField(name = "flight", index = true)
private String flight;
}

Ignite CacheJdbcPojoStoreFactory using Enum fields

I am to using the CacheJdbcPojoStoreFactory
I want to have a VARCHAR field in the database which maps to an Enum in Java.
The way I am trying to achieve this is something like the following. I want the application code to work with the enum, but the persistence to use the string so that it is human readable in the database. I do not want to use int values in the database.
This seems to work fine for creating new objects, but not for reading them out. It seems that it tries to set the field directly, and the setter (setSideAsString) is not called. Of course there is no field called sideAsString. Should this work? Any suggestions?
Here is the code excerpt
In some application code I would do something like
trade.setSide(OrderSide.Buy)
And this will persist fine. I can read "Buy" in the side column as a VARCHAR.
In Trade
private OrderSide side; // OrderSide is an enum with Buy,Sell
public OrderSide getSide() {
return side;
}
public void setSide(OrderSide side) {
this.side = side;
}
public String getSideAsString() {
return this.side.name();
}
public void setSideAsString(String s) {
this.side = OrderSide.valueOf(s);
}
Now when configuring the store, I do this
Collection<JdbcTypeField> vals = new ArrayList<>();
vals.add(new JdbcTypeField(Types.VARCHAR, "side", String.class, "sideAsString"));
After a clean start, If I query Trade using Ignite SQL query, and call trade.getSide() it will be null. Other (directly mapped) columns are fine.
Thanks,
Gordon
BinaryMarshaller deserialize only fields which used in query.
Please try to use OptimizedMarshaller:
IgniteConfiguration cfg = new IgniteConfiguration();
...
cfg.setMarshaller(new OptimizedMarshaller());
Here's the ticket for support enum mapping in CacheJdbcPojoStore.

Java 8 map with Map.get nullPointer Optimization

public class StartObject{
private Something something;
private Set<ObjectThatMatters> objectThatMattersSet;
}
public class Something{
private Set<SomeObject> someObjecSet;
}
public class SomeObject {
private AnotherObject anotherObjectSet;
}
public class AnotherObject{
private Set<ObjectThatMatters> objectThatMattersSet;
}
public class ObjectThatMatters{
private Long id;
}
private void someMethod(StartObject startObject) {
Map<Long, ObjectThatMatters> objectThatMattersMap = StartObject.getSomething()
.getSomeObject.stream()
.map(getSomeObject::getAnotherObject)
.flatMap(anotherObject-> anotherObject.getObjectThatMattersSet().stream())
.collect(Collectors.toMap(ObjectThatMatters -> ObjectThatMatters.getId(), Function.identity()));
Set<ObjectThatMatters > dbObjectThatMatters = new HashSet<>();
try {
dbObjectThatMatters.addAll( tartObject.getObjectThatMatters().stream().map(objectThatMatters-> objectThatMattersMap .get(objectThatMatters.getId())).collect(Collectors.toSet()));
} catch (NullPointerException e) {
throw new someCustomException();
}
startObject.setObjectThatMattersSet(dbObjectThatMatters);
Given a StartObject that contains a set of ObjectThatMatters
And a Something that contains the database structure already fetched filled with all valid ObjectThatMatters.
When I want to swap the StartObject set of ObjectThatMatters to the valid corresponding db objects that only exist in the scope of the Something
Then I compare the set of ObjectThatMatters on the StartObject
And replace every one of them with the valid ObjectThatMatters inside the Something object
And If some ObjectThatMatters doesn't have a valid ObjectThatMatters I throw a someCustomException
This someMethod seems pretty horrible, how can I make it more readable?
Already tried to change the try Catch to a optional but that doesn't actually help.
Used a Map instead of a List with List.contains because of performance, was this a good idea? The total number of ObjectThatMatters will be usually 500.
I'm not allowed to change the other classes structure and I'm only showing you the fields that affect this method not every field since they are extremely rich objects.
You don’t need a mapping step at all. The first operation, which produces a Map, can be used to produce the desired Set in the first place. Since there might be more objects than you are interested in, you may perform a filter operation.
So first, collect the IDs of the desired objects into a set, then collect the corresponding db objects, filtering by the Set of IDs. You can verify whether all IDs have been found, by comparing the resulting Set’s size with the ID Set’s size.
private void someMethod(StartObject startObject) {
Set<Long> id = startObject.getObjectThatMatters().stream()
.map(ObjectThatMatters::getId).collect(Collectors.toSet());
HashSet<ObjectThatMatters> objectThatMattersSet =
startObject.getSomething().getSomeObject().stream()
.flatMap(so -> so.getAnotherObject().getObjectThatMattersSet().stream())
.filter(obj -> id.contains(obj.getId()))
.collect(Collectors.toCollection(HashSet::new));
if(objectThatMattersSet.size() != id.size())
throw new SomeCustomException();
startObject.setObjectThatMattersSet(objectThatMattersSet);
}
This code produces a HashSet; if this is not a requirement, you can just use Collectors.toSet() to get an arbitrary Set implementation.
It’s even easy to find out which IDs were missing:
private void someMethod(StartObject startObject) {
Set<Long> id = startObject.getObjectThatMatters().stream()
.map(ObjectThatMatters::getId)
.collect(Collectors.toCollection(HashSet::new));// ensure mutable Set
HashSet<ObjectThatMatters> objectThatMattersSet =
startObject.getSomething().getSomeObject().stream()
.flatMap(so -> so.getAnotherObject().getObjectThatMattersSet().stream())
.filter(obj -> id.contains(obj.getId()))
.collect(Collectors.toCollection(HashSet::new));
if(objectThatMattersSet.size() != id.size()) {
objectThatMattersSet.stream().map(ObjectThatMatters::getId).forEach(id::remove);
throw new SomeCustomException("The following IDs were not found: "+id);
}
startObject.setObjectThatMattersSet(objectThatMattersSet);
}

JPA - updating an embedded entity generates invalid SQL

I am trying to update an embedded entity and JPA seems to generate the wrong SQL.
I have a Company entity with an embedded Logo entity
#Entity
public class Company {
private Long id;
#Embedded
private Logo logo;
// Omitted other fields, getters, setters, etc
}
#Embeddable
public class Logo {
private String fileName;
private String fileExtension;
private String imageDataType;
// Omitted getters and setters
}
In my DAO method I am trying to update the embedded logo like this:
#Override
public void setLogo(Logo logo, Long companyId) {
String q = "update Company c SET c.logo = :logo where c.id = :companyId";
Query query = entityManager.createQuery(q);
query.setParameter("companyId", companyId);
query.setParameter("logo", logo);
query.executeUpdate();
}
JPA (Hibernate actually) generates the following SQL.
update px_company set file_extension, file_name, file_type=(?, ?, ?) where id=?
Hibernate seems to understand it must update the three embedded logo fields, but it generates invalid SQL for it. The generated SQL results in an error.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' file_name, file_type=('jpg', '7679075394', 0) where id=1' at line 1
Any idea how I should update the embedded entity?
A bit old but just had the same issue - you should fully resolve properties of embedded classes in JPQL:
update Company c
SET c.logo.fileName = :fileName
,c.logo.fileExtension = :fileExtension
,c.logo.imageDataType= :imageDataType
where c.id = :companyId

Hibernate filter not working while indexing through hibernate search

I am trying to index an embedded collection (Set) having one-to-many association using #IndexedEmbedded.The problem is that we are only soft deleting the records in our application and I want to apply hibernate filter on the indexed collection so as to exclude the logically deleted records while indexing.
#Index
Class A {
#IndexedEmbedded
#OneToMany(targetEntity = B.class, fetch = FetchType.EAGER)
#Filter(name = "deletedRecordsFilter")
Set<B> setOfBs;
}
For Indexing :
FullTextSession fts = getFullTextSession();
fts.createIndexer(entityClass)
.purgeAllOnStart(true)
.optimizeAfterPurge(true)
.optimizeOnFinish(true)
.batchSizeToLoadObjects(30)
.threadsForSubsequentFetching(8)
.threadsToLoadObjects(4)
.threadsForIndexWriter(3)
.startAndWait();
I have enabled the filter using session.enableFilter("deletedFilterRecords"). The data is indexed but the filter is not working properly. The embedded collections still contain the deleted records.
Is that hibernate filters do not work while indexing through hibernate search or am I missing something?
If filters do not work while indexing, then is there any way so as not to index the logically deleted records?
You should use a FullTextFilter, not a standard Hibernate filter. I have used one in the project that I am currently working on. You add an annotation like the below above the definition of your indexed class:
#Indexed
#Entity
#FullTextFilterDefs( {
#FullTextFilterDef(name = "includeInSearchFilter", impl = IncludeInSearchFilterFactory.class,
cache = FilterCacheModeType.INSTANCE_AND_DOCIDSETRESULTS)
})
public class SomeEntity ...
You then need to supply the referenced factory class as well, something like this:
public class IncludeInSearchFilterFactory {
private String includeInSearchResults;
public void setIncludeInSearchResults(String includeInSearchResults) {
this.includeInSearchResults = includeInSearchResults;
}
#Key
public FilterKey getKey() {
StandardFilterKey key = new StandardFilterKey();
key.addParameter(includeInSearchResults);
return key;
}
#Factory
public Filter getFilter() {
Query query = new TermQuery(new Term("includeInSearchResults", includeInSearchResults));
return new QueryWrapperFilter(query);
}
}
In my case the "includeInSearchResults" member was an indexed field on the entity which was set to true if I wanted the object to be returned by my search else it was set to false.
To enable the full text filter:
fullTextQuery.enableFullTextFilter("includeInSearchFilter").setParameter("includeInSearchResults", "true");