Indexes: Map Indexes
Map
indexes, sometimes referred to as simple indexes, contain one (or more) mapping functions that indicate which fields from the documents should be indexed. They indicate which documents can be searched by which fields.
These mapping functions are LINQ-based functions or JavaScript function (when using JavaScript indexes) and can be considered the core of indexes.
What Can be Indexed
You can:
- index single fields
- combine multiple fields together
- index partial field data
- index nested data
- index fields from related documents
- index fields from multiple collections
- ...and more.
Indexing Single Fields
Let's create an index that will help us search for Employees
by their FirstName
, LastName
, or both.
- First, let's create an index called
Employees/ByFirstAndLastName
public static class Employees_ByFirstAndLastName extends AbstractIndexCreationTask {
// ...
}
public static class Employees_ByFirstAndLastName extends AbstractJavaScriptIndexCreationTask {
// ...
}
- The next step is to create the indexing function itself. This is done by setting the
map
field with mapping function in a parameterless constructor.
public Employees_ByFirstAndLastName() {
map = "docs.Employees.Select(employee => new { " +
" FirstName = employee.FirstName, " +
" LastName = employee.LastName " +
"})";
}
public Employees_ByFirstAndLastName() {
setMaps(Sets.newHashSet("map('Employees', function (employee){\n" +
" return {\n" +
" FirstName : employee.FirstName,\n" +
" LastName : employee.LastName\n" +
" };\n" +
" })"));
}
List<Employee> employees1 = session.query(Employee.class, Employees_ByFirstAndLastName.class)
.whereEquals("FirstName", "Robert")
.toList();
List<Employee> employees2 = session.query(Employee.class, Query.index("Employees/ByFirstAndLastName"))
.whereEquals("FirstName", "Robert")
.toList();
from index 'Employees/ByFirstAndLastName'
where FirstName = 'Robert'
Field Types
Please note that indexing capabilities are detected automatically from the returned field type from the indexing function.
For example, if our Employee
will have a property called Age
that is an integer
then the following indexing function...
from employee in docs.Employees
select new
{
Age = employee.Age
}
map('Employees', function(employee)
{
return {
Age : employee.Age
};
})
...grant us the capability to issue numeric queries (return all the Employees that Age
is more than 30).
Changing the Age
type to a string
will take that capability away from you. The easiest example would be to issue .ToString()
on the Age
field...
from employee in docs.Employees
select new
{
Age = employee.Age.ToString()
}
map('Employees', function(employee)
{
return {
Age : employee.Age.toString()
};
})
Convention
You will probably notice that in the Studio
, this function is a bit different from the one defined in the Employees_ByFirstAndLastName
class:
from employee in docs.Employees
select new
{
FirstName = employee.FirstName,
LastName = employee.LastName
}
The part you should pay attention to is docs.Employees
. This syntax indicates from which collection a server should take the documents for indexing. In our case, documents will be taken from the Employees
collection. To change the collection, you need to change Employees
to the desired collection name or remove it and leave only docs
to index all documents.
Combining Multiple Fields Together
Since each index contains a LINQ function, you can combine multiple fields into one.
Example I
Index definition:
public static class Employees_ByFullName extends AbstractIndexCreationTask {
public Employees_ByFullName() {
map = "docs.Employees.Select(employee => new { " +
" FullName = (employee.FirstName + \" \") + employee.LastName " +
"})";
}
}
public static class Employees_ByFullName extends AbstractJavaScriptIndexCreationTask {
public Employees_ByFullName() {
setMaps(Sets.newHashSet("map('Employees', function (employee){\n" +
" return {\n" +
" FullName : employee.FirstName + ' ' + employee.LastName\n" +
" };\n" +
" })"));
}
}
Query the index:
// notice that we're 'cheating' here
// by marking result type in 'query' as 'Employees_ByFullName.Result'
// and changing type using 'ofType' before sending query to server
List<Employee> employees = session
.query(Employee.class, Employees_ByFullName.class)
.whereEquals("FullName", "Robert King")
.toList();
from index 'Employees/ByFullName'
where FullName = 'Robert King'
Example II
Information
In this example, the index field Query
combines all values from various Employee fields into one. The default Analyzer on field is changed to enable Full Text Search
operations. The matches no longer need to be exact.
You can read more about analyzers and Full Text Search
here.
Index definition:
public static class Employees_Query extends AbstractIndexCreationTask {
public Employees_Query() {
map = "docs.Employees.Select(employee => new { " +
" Query = new [] { employee.FirstName, employee.LastName, employee.Title, employee.Address.City } " +
"})";
index("query", FieldIndexing.SEARCH);
}
}
public static class Employees_Query extends AbstractJavaScriptIndexCreationTask {
public Employees_Query() {
setMaps(Sets.newHashSet("map('Employees', function (employee) {\n" +
" return {\n" +
" Query : [employee.FirstName,\n" +
" employee.LastName,\n" +
" employee.Title,\n" +
" employee.Address.City]\n" +
" }\n" +
" })"));
IndexFieldOptions fieldOptions = new IndexFieldOptions();
fieldOptions.setIndexing(FieldIndexing.SEARCH);
getFields().put("Query", fieldOptions);
}
}
Query the index:
List<Employee> employees = session
.query(Employee.class, Employees_Query.class)
.search("Query", "John Doe")
.toList();
from index 'Employees/Query'
where search(Query, 'John Doe')
Indexing Partial Field Data
Imagine that you would like to return all employees that were born in a specific year. You can do it by indexing birthday
from Employee
in the following way:
Index definition:
public static class Employees_ByBirthday extends AbstractIndexCreationTask {
public Employees_ByBirthday() {
map = "docs.Employees.Select(employee => new { " +
" Birthday = employee.Birthday " +
"})";
}
}
public static class Employees_ByBirthday extends AbstractJavaScriptIndexCreationTask {
public Employees_ByBirthday() {
setMaps(Sets.newHashSet("map('Employees', function (employee){\n" +
" return {\n" +
" Birthday : employee.Birthday\n" +
" }\n" +
" })"));
}
}
Query the index:
LocalDate startDate = LocalDate.of(1963, 1, 1);
LocalDate endDate = startDate.plusYears(1).minus(1, ChronoUnit.MILLIS);
List<Employee> employees = session
.query(Employee.class, Employees_ByBirthday.class)
.whereBetween("Birthday", startDate, endDate)
.toList();
from index 'Employees/ByBirthday '
where Birthday between '1963-01-01' and '1963-12-31T23:59:59.9990000'
RavenDB gives you the ability to extract field data and to index by it. A different way to achieve our goal will look as follows:
Index defintion:
public static class Employees_ByYearOfBirth extends AbstractIndexCreationTask {
public Employees_ByYearOfBirth() {
map = "docs.Employees.Select(employee => new { " +
" YearOfBirth = employee.Birthday.Year " +
"})";
}
}
public static class Employees_ByYearOfBirth extends AbstractJavaScriptIndexCreationTask {
public Employees_ByYearOfBirth() {
setMaps(Sets.newHashSet("map('Employees', function (employee){\n" +
" return {\n" +
" Birthday : employee.Birthday.Year\n" +
" }\n" +
" })"));
}
}
Query the index:
List<Employee> employees = session
.query(Employee.class, Employees_ByYearOfBirth.class)
.whereEquals("YearOfBirth", 1963)
.toList();
from index 'Employees/ByYearOfBirth'
where YearOfBirth = 1963
Indexing Nested Data
If your document contains nested data, e.g. Employee
contains Address
, you can index on its fields by accessing them directly in the index. Let's say that we would like to create an index that returns all employees that were born in a specific Country
:
Index definition:
public static class Employees_ByCountry extends AbstractIndexCreationTask {
public Employees_ByCountry() {
map = "docs.Employees.Select(employee => new { " +
" Country = employee.Address.Country " +
"})";
}
}
public static class Employees_ByCountry extends AbstractJavaScriptIndexCreationTask {
public Employees_ByCountry() {
setMaps(Sets.newHashSet("map('Employees', function (employee){\n" +
" return {\n" +
" Country : employee.Address.Country\n" +
" }\n" +
" })"));
}
}
Query the index:
List<Employee> employees = session
.query(Employee.class, Employees_ByCountry.class)
.whereEquals("Country", "USA")
.toList();
from index 'Employees/ByCountry'
where Country = 'USA'
If a document relationship is represented by the document's ID, you can use the LoadDocument
method to retrieve such a document. More about it can be found here.
Indexing Multiple Collections
Read the article dedicated to Multi-Map
indexes here.