Indexes

Indexing is a strategy for expediting retrievals from a database by storing values in a separate table. This section describes how Dari implements indexing.

Indexed Fields

Dari implements indexing by requiring searches to be performed on indexed fields; searches are not allowed on non-indexed fields. You declare an indexed field by applying the @Recordable.Indexed annotation.

Class With Indexed Field
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import com.psddev.dari.db.Record;

public class User extends Record {

   @Indexed
   private String userName;

   private String screenName;

}

In the previous snippet—

  • Lines 5–6 declare a field userName with the @Indexed annotation. When a new instance of User is created and saved in the database, Dari creates a parallel index entry.
  • Line 8 declares a field screenName without an annotation. New instances of User save the value for screenName, but Dari does not create an index entry.

Given the field definitions in the previous snippet, you can search for specific instances of User using the Query#where method on the indexed field.

List<User> = Query.from(User.class).where("userName = 'zombie'").selectAll();

The previous snippet returns all User records with userName zombie.

In contrast, you cannot search for specific instances of User records using the Query#where method on a non-indexed field.

List<User> = Query.from(User.class).where("screenName = 'vampire'").selectAll();

The previous snippet returns an error because the field screenName is not indexed.

Keep in mind the following when indexing fields:

  • By default, fields are not indexed. You must explicitly apply the annotation @Indexed to a field for Dari to index it.
  • You cannot apply @Indexed to fields with the modifier transient.

See also:

Indexed Methods

Dari can index the results of methods, so you can quickly retrieve all of the records that generate a particular result. In the following example, the getFullName method is indexed. When a User object is created, Dari automatically executes the method and stores the result in an index.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import com.psddev.dari.db.Record;

public class User extends Record {

    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    @Indexed
    public String getFullName() {
        return (getFirstName() + " " + getLastName());
    }

}

In the previous snippet—

  • Lines 17–19 define a method getFullName that uses the results of the methods getFirstName and getLastname to return a user’s full name.
  • Line 16 applies the annotation @Indexed to the result of the method getFullName.

You can search for instances of User having a specific full name.

List<User> = Query.from(User.class).where("getFullName = 'John Adams'").selectAll();

The previous snippet returns all User records whose full name is John Adams.

You can apply the @Indexed annotation to methods with the following characteristics:

  • Starts with a get, is, or has literal, such as getName, isActive, or hasEntry.
  • Returns a value (does not return void).
  • Has public access.

See also:

Strategies for Effective Indexing

Indexing a database requires additional storage and processing time beyond that required to save a record. Every time you instantiate a new object with an indexed field, Dari stores the object itself and then adds a new index entry for each indexed field; similar operations occur when you modify an indexed field’s value, modify the value of a field used in an indexed method, and when you delete an object with indexed fields. For example, referring to the snippet Class With Indexed Field, when you create a new instance of User

  • Dari stores the new instance in the database.
  • Dari creates a new index entry for the field userName.

The extra processing required for indexing impacts response times as the size of table holding the affected class grows. As a result, a common best practice is to index only those fields and methods you expect will be used in where clauses.

Excluding Records from Queries with Visibility Labels

A visibility label is a property with the annotation @Indexed(visibility = true). This annotation can exclude a record from being included in a query—even if the record satisfies the query’s where clause. If the annotated field’s value is non-null, Dari excludes the entire record from the results.

Class With @Indexed(visibility)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import com.psddev.dari.db.Record;

public class User extends Record {

    @Indexed(visibility = true)
    private Boolean queryVisible;

    @Indexed
    private String userName;

}

In the previous snippet—

  • Line 5 applies an @Indexed annotation with the element visibility = true.
  • Line 6 declares a visibility label queryVisible as a Boolean.
  • Lines 8–9 declare a field userName that is indexed.

Given the previous snippet, suppose there is an instance of User with userName = John Adams and queryVisible is Boolean.FALSE. Under those conditions, the following query returns no records.

List<User> = Query.from(User.class).where("userName = 'John Adams'").selectAll();

However, if for a particular object queryVisible is null, the query retrieves the object.

The following table summarizes the behavior of visibility labels.

Visibility label’s value Visible to queries?
null Yes
non-null No

Best Practices for Working With Visibility Labels

Use Boolean Type

Use properties of type Boolean, not the Java primitive boolean, as a visibility label. Fields of type Boolean can be set to null, while primitive booleans must have a non-null value of true or false. In addition, the type Boolean has an intuitive meaning of “on” or “off.”

Set Visibility Value Before Saving the Object

The snippet Class With @Indexed(visibility) declares a property queryVisible as a visibility label. Other methods within your application may inadvertently change this property’s value to null (in which case the object is visible to queries) or to a non-null value (in which case the object is invisible to queries). As a best practice, implement the callback Record#beforeSave to set the visibility label before Dari commits the object to the database.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import com.psddev.dari.db.Record;

public class User extends Record {

    @Indexed(visibility = true)
    private Boolean queryVisible;

    public void setQueryVisible(Boolean queryVisible) {
        this.queryVisible = queryVisible;
    }

    @Override
    protected void beforeSave() {
        setQueryVisible(null);
    }

}

In the previous snippet, lines 12–15 implement the Record#beforeSave method to set the visibility label to null, making the object visible to queries.

See also:

Retrieving Records With Visibility Labels

The section Excluding Records from Queries with Visibility Labels describes how to exclude records from a query that otherwise satisfy a where condition. You can retrieve excluded records by examining the _id field that Dari automatically assigns to all objects. Suppose you have an object instantiated from the snippet Class With @Indexed(visibility), and you perform the following query:

User oneUser = Query.from(User.class).where("_id = '123456789'").first();

The previous snippet retrieves a user with ID 123456789 even if the field queryVisible is not null.

Reindexing Existing Objects

Applying the @Indexed annotation, building, and redeploying your project does not automatically index existing objects. If you apply an @Indexed annotation to fields and there are objects with those fields already in the database, you need to manually reindex the existing objects. For details, see Reindexing.