Indexes

Fields must be indexed in a database in order to be queried. To query on an unindexed field results in an exception similar to the following:

Can't query [com.psddev.dari.test.User/userName] because it's not indexed! (com.psddev.dari.db.Query$NoIndexException)

Fields are indexed by applying the @Recordable.Indexed annotation on them. For example, by applying the @Indexed annotation to username, you can retrieve User objects by searching on the userName field.

public class User extends Record
{
   @Indexed
   private String userName;

   ...
}

In Dari, the index hierarchy is completely flat; Dari has no notion of content type in using indexes. For example, if you have five indexes in an Article class and seven indexes in a Gallery class, Dari does not identify the indexes by type. Dari only sees 12 indexes total in the system.

Dari uses a <className>.<fieldNamePrefix>.<fieldName> pathing syntax to identify indexes, where <fieldNamePrefix> is optional. For example, with the following query …

Query.from(brightspot.tutorial.article.Article.class).where("headline = 'New Insights'");

… Dari translates the left-hand expression of the predicate to the fully-qualified path of “brightspot.tutorial.article.Article/headline”, using the path as a lookup mechanism to the applicable index. In Dari, every index is uniquely identified, so there is no risk of retrieval of unintended data.

Indexed Methods

In addition to fields, you can apply the @Indexed annotation to any method that returns a value. The method must start with “get”, “is”, or “has”. This feature allows for calculated values to be queried.

In the following example, the getFullName method is indexed. When a User object is created, Dari automatically executes the method, which calculates the full name of the user. Note that Dari uses an internal field to persist the value of an indexed method; you do not have to declare a field to persist the value of an indexed method.

public class User extends Record
{
   @Indexed
   private String userName;
   private String firstName;
   private String lastName;

   // getters and setters
   public String getUserName() {
     return userName;
   }
   public void setUserName(String userName) {
     this.userName = userName;
   }

   public String getFirstName() {
     return firstName;
   }
   public void setFirstName(String firstName) {
     this.firstName = firstName;
   }

   public String getLastName() {
     return lastName;
   }

   public void setLastName(String lastName) {
     this.lastName = lastName;
   }

   // Creates a full name value from the firstName and lastName values.
   @Indexed
   public String getFullName() {
     return (getFirstName()+ " " + getLastName());
   }
}

To query on an indexed method, you must specify the method name to test field values. For example, to search for a User where the full name is “Tom Bridges”, you would specify the method name:

return Query.from(User.class).where("getFullName = 'Tom Bridges'").first();

Note

  • All supported field types can be @Indexed. Fields marked with the Java transient keyword cannot be indexed.
  • @Indexed cannot be applied to void methods or non-public access methods.
  • Only add @Indexed to fields that are likely to be queried. Every field or method marked with an index results in an additional row written to the underlying database index tables when using a SQL database backend. Indiscriminate indexing potentially creates unnecessary rows and can result in poor performance in systems with large amounts of data.

Visibility-Indexed Fields

A visibility-indexed field is one in which the value of the field determines the object’s visibility in a query for published objects. If the field is set to a non-null value, then the object is hidden from the query. If the field is set to null, the query retrieves the object.

A field is visibility-indexed when the @Indexed(visibility=true) annotation is applied to it. In the following code snippet, status is a visibility-indexed field.

 public class Application extends Record {

    @Indexed(visibility = true)
    private Boolean status; // set to null by default

    ...
}

If status is set to true or false, then the Application object is not returned in a query, even if Application includes additional visibility-indexed fields that are set to null. The Application JSON object shows that status is a visibility-indexed field set to true.

{
   "name" : "Janet Renaldo",
   "address" : "1544 Haldane Ln. Decatur, Ga. 30032",
   "application" : {
     "storage" : "Local file storage",
     "path" : "/Users/hr/resumes/app_68b716x33.pdf",
     "contentType" : "application/pdf",
     "metadata" : null
   },
   "status" : true,
   "dari.visibilities" : [ "status" ],
   "_id" : "0000015b-b4b4-dcaa-a5df-fdfc9bec0000",
   "_type" : "0000015b-b4a1-dcaa-a5df-fde9e2820000"
}

Note

Because fields of type Boolean can be set to null (unlike a boolean primitive), visibility indexing is often applied to fields of that type. If you follow this pattern, keep in mind that some applications might automatically change null Boolean fields to false, inadvertently hiding the objects from queries. Therefore, you should always test the value of the field before saving the object. For example, you can implement the Record#beforeSave callback method to test such fields, and reset them to null if applicable. For more information, see Persistence APIs.

A query for published Application objects will not return the above object, for example:

Query.from(Application.class).selectAll();  // does not return hidden objects

However, you can return a specified hidden object with a Query#fromAll or Query#from method that filters on the Id of the object:

Application appl = (Application) Query.fromAll().where("_id = ?", "0000015b-b4a1-dcaa-a5df-fde9e2820000").first();

or …

Application appl = (Application) Query.from(Application.class).where("_id = ?", "0000015b-b4a1-dcaa-a5df-fde9e2820000").first();

You can return all hidden objects with the Query#from method that filters on the visibility-indexed field:

List<Application> applications = Query.from(Application.class).where("status = ?", "true").selectAll();

Dari hides an object by changing its type Id. When a visibility-indexed field is set to a non-null value, Dari creates a hash of the original type Id with the name and the value of the field. (The original type Id is still preserved in the record.) Objects with changed type Ids are ignored by queries for published objects.

You can retrieve the original type Id and the hashed type Id of a hidden object. In the following code snippet, a hidden article is retrieved using Query#fromAll. The State#getTypeId method returns the original type Id. The State#getVisibilityAwareTypeId gets the hashed type Id. (If the retrieved object is not hidden, the two methods return the same value.)

Application appl = (Application) Query.fromAll().where("_id = ?", "0000015b-b4a1-dcaa-a5df-fde9e2820000").first();
System.out.println("Original typeId: " + appl.getState().getTypeId());
System.out.println("Hashed typeId: " + appl.getState().getVisibilityAwareTypeId());

Note

You can leverage visibility-indexed fields to define non-published states that are managed in Brightspot. For more information, see Visibility Labels.

Reindexing Existing Objects

Sometimes it is necessary to index fields of existing Model classes that already have data. Indexing fields or methods of an existing class does not retroactively index previously persisted data; it only ensures that future data will be indexed. To index persisted objects in a database, Dari provides the Bulk Operations tool.