Highlight Search Results

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