Querying: Highlighting
Another feature called Highlighting
has been added to RavenDB to enhance the search UX.
Setup
public class Blog {
private String id;
private String title;
private String category;
private String content;
private Date publishedAt;
private String[] tags;
private List<BlogComment> comments;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getPublishedAt() {
return publishedAt;
}
public void setPublishedAt(Date publishedAt) {
this.publishedAt = publishedAt;
}
public String[] getTags() {
return tags;
}
public void setTags(String[] tags) {
this.tags = tags;
}
public List<BlogComment> getComments() {
return comments;
}
public void setComments(List<BlogComment> comments) {
this.comments = comments;
}
}
public class BlogComment {
private String title;
private String content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
public static class BlogPosts_ByContent extends AbstractIndexCreationTask {
public static class Result {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
public BlogPosts_ByContent() {
map = "docs.Posts.Select(post => new { post.content })";
index("content", FieldIndexing.SEARCH);
store("content", FieldStorage.YES);
termVector("content", FieldTermVector.WITH_POSITIONS_AND_OFFSETS);
}
}
Important
Each of the fields on which we want to use Highlighting needs to have:
- FieldIndexing set to
SEARCH
- FieldStorage set to
YES
- FieldTermVector set to
WITH_POSITIONS_AND_OFFSETS
Usage
To use Highlighting we just need to use one of the highlight
query methods. The basic usage can be as simple as:
Reference<Highlightings> highlightsRef = new Reference<>();
List<Blog> results = session
.advanced()
.documentQuery(Blog.class, BlogPosts_ByContent.class)
.highlight("content", 128, 1, highlightsRef)
.search("content", "raven")
.toList();
StringBuilder builder = new StringBuilder();
builder.append("<ul>");
for (Blog result : results) {
String[] fragments = highlightsRef.value.getFragments(result.getId());
builder.append("<li>")
.append(fragments[0])
.append("</li>");
}
builder.append("</ul>");
String ul = builder.toString();
This will return the list of results and for each result we will be displaying first found fragment with the length up to 128 characters.
Highlighting + Projections
Highlighting can also be done when projections are performed.
Reference<Highlightings> highlightsRef = new Reference<>();
HighlightingOptions highlightingOptions = new HighlightingOptions();
highlightingOptions.setPreTags(new String[] { "**" });
highlightingOptions.setPostTags(new String[] { "**" });
List<BlogPosts_ByContent.Result> results = session
.query(BlogPosts_ByContent.class, BlogPosts_ByContent.class)
.highlight("content", 128, 1, highlightingOptions, highlightsRef)
.search("content", "raven")
.selectFields(BlogPosts_ByContent.Result.class)
.toList();
public static class BlogPosts_ByContent extends AbstractIndexCreationTask {
public static class Result {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
public BlogPosts_ByContent() {
map = "docs.Posts.Select(post => new { post.content })";
index("content", FieldIndexing.SEARCH);
store("content", FieldStorage.YES);
termVector("content", FieldTermVector.WITH_POSITIONS_AND_OFFSETS);
}
}
Highlighting + Map-Reduce
Highlighting can be performed when executing queries on map-reduce indexes.
// highlighting 'content', but marking 'category' as key
Reference<Highlightings> highlightsRef = new Reference<>();
HighlightingOptions highlightingOptions = new HighlightingOptions();
highlightingOptions.setPreTags(new String[] { "**" });
highlightingOptions.setPostTags(new String[] { "**" });
highlightingOptions.setGroupKey("category");
List<BlogPosts_ByCategory_Content.Result> results = session
.advanced()
.documentQuery(BlogPosts_ByCategory_Content.Result.class, BlogPosts_ByCategory_Content.class)
.highlight("content", 128, 1, highlightingOptions, highlightsRef)
.search("content", "raven")
.toList();
// get fragments for 'news' category
String[] newsHighlightings = highlightsRef.value.getFragments("news");
public static class BlogPosts_ByCategory_Content extends AbstractIndexCreationTask {
public static class Result {
private String category;
private String content;
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
public BlogPosts_ByCategory_Content() {
map = "docs.Posts.Select(post => new { post.category, post.content })";
reduce = "results.GroupBy(result => result.Category).Select(g => new {" +
" category = g.Key, " +
" Content = string.Join(\" \", g.Select(r => r.content)) " +
"}";
index("content", FieldIndexing.SEARCH);
store("content", FieldStorage.YES);
termVector("content", FieldTermVector.WITH_POSITIONS_AND_OFFSETS);
}
}
Remarks
Note
Default <b></b>
tags are coloured and colours are returned in following order:
- yellow,
- lawngreen,
- aquamarine,
- magenta,
- palegreen,
- coral,
- wheat,
- khaki,
- lime,
- deepskyblue,
- deeppink,
- salmon,
- peachpuff,
- violet,
- mediumpurple,
- palegoldenrod,
- darkkhaki,
- springgreen,
- turquoise,
- powderblue