Reputation: 768
I have two objects - a Document
object, and a Hit
object:
Document
id
description
hits (Collection of type Hits)
...
Hit
documentId (Type Document)
date
...
I'm attempting to create a Hibernate Search query that searches the document description, then orders the results by the count of hits within a given date range.
I'm having a bit of trouble wrapping my head around how to do this. I've taken a look at this, but it takes into account the total number of hits, and does not allow for dynamic date range queries, as the bridged field is updated independent of date.
I currently have an index for my Document
object with searching on description
working fine. I'm thinking I may need to create an index for the Hit
object, run a query against it for the date range, group by documentId
, and then run a second query using the searched term against the Document
index.
Given this strategy, I'm still not sure how to:
documentId
My guess is that I would need to get all results from the Hits
index (using some type of faceting logic for grouping/ordering maybe?), then match on documentId
/term and handle pagination in the second query. I'm just not sure how all of this would look when it comes to building a query with the QueryBuilder interface, or if there's a completely different way to approach this that I haven't thought of.
UPDATE
public class DateRangeDownloadsFieldComparator extends FieldComparator<Integer> {
class DownloadsParser implements FieldCache.IntParser {
@Override
public int parseInt(String string) {
/*
* What do I pass here and what do I do with it?
* Ideally, I would pass the downloads collection and return the size of the
* collection where the download appears within the provided date range, but
* passing a collection here does nothing, as it's silently ignored.
*/
return 0;
}
}
private Calendar startDate;
private Calendar endDate;
private final int[] fieldValues;
private int bottom;
private int[] currentReaderValues;
public DateRangeDownloadsFieldComparator(int numHits, Calendar startDate, Calendar endDate) {
super();
this.startDate = startDate;
this.endDate = endDate;
fieldValues = new int[numHits];
}
@Override
public int compare(int slot1, int slot2) {
return compareValues(fieldValues[slot1], fieldValues[slot2]);
}
@Override
public int compareBottom(int doc) throws IOException {
int currentDoc = currentReaderValues[doc];
return compareValues(bottom, currentDoc);
}
@Override
public int compareValues(Integer v1, Integer v2) {
if (v1 > v2) {
return 1;
}
else if (v1 < v2) {
return -1;
}
else {
return 0;
}
}
@Override
public void copy(int slot, int doc) throws IOException {
int v1 = currentReaderValues[doc];
fieldValues[slot] = v1;
}
@Override
public void setBottom(int slot) {
bottom = fieldValues[slot];
}
@Override
public void setNextReader(IndexReader reader, int docBase) throws IOException {
currentReaderValues = FieldCache.DEFAULT
.getInts(reader, "downloads", new DownloadsParser());
}
@Override
public Integer value(int slot) {
return fieldValues[slot];
}
}
UPDATE 2
Document Entity
@Entity
@Table(name = "documents")
@Indexed(index = "documents")
public class EDocument {
public static final String FIELD_NAME = "name";
public static final String FIELD_CREATED = "created";
public static final String FIELD_DESCRIPTION = "description";
@Id
@GeneratedValue
@Column(name = "id")
private Long id;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created")
@Field(name = FIELD_CREATED)
private Calendar created;
@Column(name = "name")
@Field(name = FIELD_NAME)
private String name;
@Column(name = "description")
@Field(name = FIELD_DESCRIPTION)
private String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@IndexedEmbedded(depth = 1)
private EUser user;
@OneToMany(
fetch = FetchType.LAZY,
targetEntity = EDownload.class,
mappedBy = "document",
orphanRemoval = true,
cascade = CascadeType.ALL)
@IndexedEmbedded(depth = 1)
private Set<EDownload> downloads;
public EDocument() {
created = Calendar.getInstance();
}
// getters and setters
}
Download Entity
@Entity
@Table(name = "downloads")
public class EDownload {
public static final String FIELD_REQUESTED = "requested";
@Id
@GeneratedValue
@Column(name = "id")
private Long id;
@Column(name = "requested")
@Field(name = FIELD_REQUESTED)
private Calendar requested;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "document_id", nullable = false)
private EDocument document;
public EDownload() {
requested = Calendar.getInstance();
}
// getters and setters
}
Upvotes: 0
Views: 662
Reputation: 768
I ended up going with something similar to what @gmansoor suggested - a first query for the terms, which returns IDs, and then a second query based on those IDs. It doesn't appear that there's a way to do this in a single query in Hibernate Search, so this seems to be the best solution for now. If anyone knows of a better way to do this, please let me know.
Upvotes: 0
Reputation: 19129
I probably would index the hits as part of the document index using @IndexedEmbedded
. Then you run your query against the description
field, providing a custom Sort
implementation. The custom sort will need the data range as parameter and then count the hits in this date range and order accordingly.
Upvotes: 0