-
Notifications
You must be signed in to change notification settings - Fork 297
Query Builder
The CBLQueryBuilder class provides a high-level interface for defining database queries, somewhat like Core Data's NSFetchRequest. Instead of thinking about map functions and key ranges, you provide "select", "where" and "order by" expressions; the query builder uses these to define a view and configure the query.
CBLQueryBuilder does not make CBLView obsolete! It doesn't support everything you can do with map/reduce, so there are cases where you'll run into its limits and need to define your own view and queries. But it does take care of most common types of queries.
Compatibility: CBLQueryBuilder is a new class in Couchbase Lite 1.1. (An earlier version was formerly available in version 1.0.3 as an unsupported 'extra', but not built into the library.) It is iOS/Mac specific, since it's based on Cocoa classes like NSPredicate and NSSortDescriptor. Comparable functionality will be available on other platforms too, with APIs that are idiomatic for those platforms.
A CBLQueryBuilder defines a query or a family of related queries (since it can contain variables whose values can be substituted later.) It has four attributes:
- The database to query.
- A "where" predicate (boolean-valued function) that chooses which documents match the query.
- An array of values to select from the matching documents. Each value is an expression based on properties of the document.
- An optional array of sort descriptors that determine the order the results appear.
(This may look something like a SQL SELECT
statement, but the details are different.)
Based on these attributes, the query builder will create a view and register its map function. You don't need to worry about this. The query builder will create a pre-configured CBLQuery object for you, and all you need to do is run it and use the result.
Often a query's predicate will depend on specific values that aren't known until runtime, such as a minimum or maximum property value. You can specify these as as placeholder variables; in a predicate string they're $
-prefixed identifiers. When creating a query, you provide specific values for these variables.
The best practice is to create query builders ahead of time, keeping references to them in global variables or long-lived instance variables. This reduces the overhead of parsing the attributes and creating the views. If you're creating a query builder each time you run a query, you're probably doing it wrong.
Let's assume a simple blog database that we want to search for posts tagged with a given keyword. The posts will look like this:
{
"type": "post",
"title": "Introducing CBLQueryBuilder",
"author": "Jens Alfke",
"date": "2014-12-11",
"body": "Yo, 'sup. Check out this new class...",
"tags": ["couchbase lite", "iOS", "queries", "views", "API"]
}
When the application's search controller object is initialized, it creates a query builder:
self.tagQueryBuilder = [[CBLQueryBuilder alloc]
initWithDatabase: self.database
select: @[@"title", @"body", @"author", @"date"]
where: @"type == 'post' and tags contains $TAG"
orderBy: @[@"-date"]
error: &error];
We are limiting the search to documents whose type
property is "post"
, since there are likely to be other types of documents (comments, authors...) Also, we don't know the specific tag to search for, so we leave it as a variable $TAG
.
The language used in the where:
parameter is Apple's predicate syntax, not SQL or N1QL or any other query language.
The orderBy:
array is using the string @"-date"
as a shortcut meaning "in descending order of date
property". This could also have been specified as an NSSortDescriptor object.
Then when a tag is given, the controller can perform a query:
- (void) queryForTag: (NSString*)tag {
NSError* error;
CBLQueryEnumerator* e = [self.tagQueryBuilder runQueryWithContext: @{@"TAG": tag}
error: &error];
if (e) {
[self displayRows: e.allObjects];
} else {
[self handleError: error];
}
}
To create a query, we have to give a context dictionary that provides a value for each variable, in this case TAG
.
Important: The $
prefix used in the predicate is not part of the variable name itself, and isn't used in the dictionary key. If you put it in by accident, you'll get a runtime exception complaining that the variable TAG
doesn't have a value.