see on GitHub

Indexes: Indexing Related Documents

To extend indexing capabilities and simplify many scenarios, we have introduced the possibility for indexing related documents.

Example I

Let's consider a simple Product - Category scenario where you want to look for a Product by Category Name.

Without this feature, you would have to create a fairly complex multiple map-reduce index. This is why the LoadDocument function was introduced.

public static class Products_ByCategoryName extends AbstractIndexCreationTask {
    public static class Result {
        private String categoryName;

        public String getCategoryName() {
            return categoryName;
        }

        public void setCategoryName(String categoryName) {
            this.categoryName = categoryName;
        }
    }

    public Products_ByCategoryName() {
        map = "docs.Products.Select(product => new { " +
            "    CategoryName = (this.LoadDocument(product.Category, \"Categories\")).Name " +
            "})";
    }
}
IndexDefinition indexDefinition = new IndexDefinition();
indexDefinition.setName("Products/ByCategoryName");
indexDefinition.setMaps(Collections.singleton("from product in products " +
    "   select new " +
    "   { " +
    "       CategoryName = LoadDocument(product.Category, \"\"Categories\"\").Name " +
    "   }"));

store.maintenance().send(new PutIndexesOperation(indexDefinition));
public static class Products_ByCategoryName extends AbstractJavaScriptIndexCreationTask {
    public static class Result {
        private String categoryName;

        public String getCategoryName() {
            return categoryName;
        }

        public void setCategoryName(String categoryName) {
            this.categoryName = categoryName;
        }
    }

    public Products_ByCategoryName() {
        setMaps(Sets.newHashSet("map('products', function(product ){\n" +
            "            return {\n" +
            "                CategoryName : load(product .Category, 'Categories').Name,\n" +
            "            }\n" +
            "        })"));
    }
}

Now we will be able to search for products using the CategoryName as a parameter:

List<Product> results = session
    .query(Products_ByCategoryName.Result.class, Products_ByCategoryName.class)
    .whereEquals("CategoryName", "Beverages")
    .ofType(Product.class)
    .toList();

Example II

Our next scenario will show us how indexing of more complex relationships is also trivial. Let's consider the following case:

public static class Book {
    private String id;
    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public static class Author {
    private String id;
    private String name;
    private List<String> bookIds;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getBookIds() {
        return bookIds;
    }

    public void setBookIds(List<String> bookIds) {
        this.bookIds = bookIds;
    }
}

To create an index with Author Name and list of Book Names, we need do the following:

public static class Authors_ByNameAndBooks extends AbstractIndexCreationTask {
    public static class Result {
        private String name;
        private List<String> books;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<String> getBooks() {
            return books;
        }

        public void setBooks(List<String> books) {
            this.books = books;
        }
    }

    public Authors_ByNameAndBooks() {
        map = "docs.Authors.Select(author => new { " +
            "    name = author.name, " +
            "    books = author.bookIds.Select(x => (this.LoadDocument(x, \"Books\")).name) " +
            "})";
    }
}
IndexDefinition indexDefinition = new IndexDefinition();
indexDefinition.setName("Authors/ByNameAndBooks");
indexDefinition.setMaps(Collections.singleton("from author in docs.Authors " +
    "     select new " +
    "     { " +
    "         name = author.name, " +
    "         books = author.bookIds.Select(x => LoadDocument(x, \"\"Books\"\").id) " +
    "     }"));
store.maintenance().send(new PutIndexesOperation(indexDefinition));
public static class Authors_ByNameAndBookNames extends AbstractJavaScriptIndexCreationTask {
    public static class Result {
        private String name;
        private List<String> books;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<String> getBooks() {
            return books;
        }

        public void setBooks(List<String> books) {
            this.books = books;
        }
    }

    public Authors_ByNameAndBookNames() {
        setMaps(Sets.newHashSet("map('author', function(a){\n" +
            "            return {\n" +
            "                name: a.name,\n" +
            "                books: a.booksIds.forEach(x => load(x, 'Book').name)\n" +
            "            }\n" +
            "        })"));
    }
}

List<Author> results = session
    .query(Authors_ByNameAndBooks.Result.class, Authors_ByNameAndBooks.class)
    .whereEquals("name", "Andrzej Sapkowski")
    .whereEquals("books", "The Witcher")
    .ofType(Author.class)
    .toList();

Remarks

Information

Indexes are updated automatically when related documents change.

Warning

Using the LoadDocument adds a loaded document to the tracking list. This may cause very expensive calculations to occur, especially when multiple documents are tracking the same document.