The Couchbase Lite 2.0 Release, brings a plethora of new features and enhancements. We discussed one such enhancement, namely the new replication protocol between in an earlier blog post on Replication 2.0. In this blog post, we will introduce you to another major feature – the Couchbase Lite Query Interface. The new query interface is based on N1QL, Couchbase’s declarative query language that extends SQL for JSON. If you are familiar with SQL, you will feel right at home with the semantics of the new API.
This post introduces you to the query interface and covers the basics. Refer to the last section of this post for specifics on other related blog posts on more advanced query capabilities.
You can download the Couchbase Mobile 2.0 pre-release builds from our downloads page.
Background
If you were using 1.x versions of Couchbase Mobile, you are probably familiar Map-Views for creating indexes and queries. In 2.0, you no longer have to create views and map functions! Instead, a simple interface allows you to create indexes and you can use a Query Builder interface to construct your queries. The new query interface is simpler to use and much more powerful in comparison. We will discover some of it’s features in this post.
Sample Project
While the examples discussed here use Swift for iOS, note that barring some minor differeces, the same query interface is supported on the Android and Windows platforms as well. So with some minor tweaks, you should be able to reuse the examples in this post when working with other platforms.
Follow instructions below if you are interested in a sample Swift Project
- Clone the iOS Swift Playground from GitHub
1$ git clone https://github.com/couchbaselabs/couchbase-lite-ios-api-playground - Follow the installation instructions in the corresponding README file to build and execute the playground.
Sample Data Model
We shall use the Travel Sample database located here
The sample data set includes several types of documents as identified by the type
property in the document. We will focus on documents of type
“hotel” and “landmark”. The JSON document model is shown below. For simplicity, we have omitted some of the properties from the model below.

The Basics
Opening / Creating a Database
For all the queries below, we will use the Database
API to open/create CouchbaseLite Database.
1 2 |
var options = DatabaseConfiguration() let db = try Database(name: kDBName, config: options) |
Indexes
To speed up read queries, you can create Indexes on properties that you will query on. The performance improvement would be significant on large datasets. Of course, be aware that there will be an increase in storage needs in order to store the indexes and performance of writes can also be impacted. So be cautious of creating too many indexes.
The following example creates a ValueIndex
on the type
property of a Document
1 |
try db.createIndex(IndexBuilder.valueIndex(items: ValueIndexItem.property("type")),withName: "typeIndex") |
The following example creates a ValueIndex
on type
and name
properties of a Document
1 |
try db.createIndex(IndexBuilder.valueIndex(items: ValueIndexItem.property("type"),ValueIndexItem.property("name")),withName: "TypeNameIndex") |
Fetching documents from database
A Query in Couchbase Lite is constructed using the Query builder API.
The query below fetches all documents from specfied database. For every document matching the query, all the properties associated with the document are fetched.
1 2 3 |
let searchQuery = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(db)) |
Fetching documents from database with pagination
This is the structure of a simple query which fetches limit
number of documents from database starting at specified offset
. For every document matching the query, all the properties associated with the document are fetched.
1 2 3 4 |
let searchQuery = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(db)) .limit(Expression.int(limit),offset: Expression.int(offset)) |
Return Values using SelectResult
A SelectResult
represents a single return value of the query statement. Documents in Couchbase Lite comprise of the document properties specified as a Dictionary of Key-Value pairs and associated metadata. The metadata consists of document Id and sequence Id associated with the Document. When you query for a document, the document metadata is not returned by default. You will need to explicitly query for the metadata.
SelectResult.all()
- Returns all properties associated with a Document
SelectResult(Expression)
- Returns properties of a Document based on the
Expression
. We discuss the various types of expressions a litte later.
- Returns properties of a Document based on the
SelectResult.expression(Expression.Meta.id)
- Returns document Id
SelectResult.expression(Expression.Meta.sequence)
- Returns sequence Id (used in replications)
You can specify a comma separated list of SelectResult
expressions in the select
statement of your Query.
For instance the following select statement queries for the document Id as well as the type
and name
properties of the document
1 2 3 |
select(SelectResult.expression(Expression.Meta.id), SelectResult.expression(Expression.property("type")), SelectResult.expression(Expression.property("name"))) |
Query Expression
A Query Expression is used for constructing a Query Statement
Couchbase Lite includes support for the following Expression
Types.
- Property
- Aggregate
- String
- Collation
- Regex
- FTS
- Null Check Operator
- Arithmetic Expressions
- Metadata
- Pattern Matching
- Comparison
- Collection
- Function
- Parameter
- Quantified
- Bitwise Operator
Processing Query Responses
The results of the query run is an array where every member of the array is an Dictionary / Map corresponding to a document that satisfies the query.
1 2 3 |
select(SelectResult.expression(Expression.Meta.id), SelectResult.expression(Expression.property("type")), SelectResult.expression(Expression.property("name"))) |
- If you are querying for all the properties of the document using
SelectResult.all()
, then each member of the response array is a Key-Value pair, where the database name is the key and the dictionary corresponding to the Document is the value for the key.For instance,consider the query
1 2 3 4 |
let searchQuery = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(db)) .limit(Expression.int(limit),offset: Expression.int(offset)) |
- The results would look something like this (where “travel-sample” is the name of the database )
12345678910111213141516171819202122232425[{"travel-sample": {"callsign": "MILE-AIR","country": "United States","iata": "Q5","icao": "MLA","id": 10,"name": "40-Mile Air","type": "airline"}},{"travel-sample": {"callsign": "TXW","country": "United States","iata": "TQ","icao": "TXW","id": 10123,"name": "Texas Wings","type": "airline"}}]
The code snippet below is an example (in Swift) of how you would process the above results to get the details of the document object
12345678for row in try searchQuery.execute() {if let dict = row.toDictionary() as? [String:Any],let docObject = dict["travel-sample"] as? [String:Any] {// You can now process the document propertieslet name = docObject["name"]let type = docObject["type"]}}
- If you are querying for Id of the document using
SelectResult.expression(Expression.Meta.id)
, then each member of the response array is a Key-Value pair, with"id"
as the key and the the document Id as the value.
For instance,consider the query
1 2 3 4 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id)) .from(DataSource.database(db)) .limit(Expression.int(limit)) |
- The results would look something like this –
1234567891011[{"id": "airline_112"},{"id": "airline_189"},{"id": "airline_1209"}]
The code snippet below is an example (in Swift) of how you would process the above results to get the details of the document object using the document Id
1234567for row in try searchQuery.execute() {if let dict = row.toDictionary() as? [String:Any],let docId = dict["id"] as? String {// You can now fetch the details of the document using the Idlet doc = try db.getDocument(docId)}} - If you are querying for specific property / properties of the document using for instance,
SelectResult.expression(Expression.property("type")
, then each member of the response array is a Key-Value pair, with the name of the property / properties as the key(s) and the corresponding property values as the valueFor instance,consider the query
1 2 3 4 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Expression.property("type"))) .from(DataSource.database(db)) .limit(Expression.int(limit)) |
- The results would look something like this –
1234567891011[{"type": "airline"},{"type": "airline"},{"type": "hotel"}]
The code snippet below is an example (in Swift) of how you would process the above results to get the property values that you queried for
123456for row in try searchQuery.execute() {if let docObject = row.toDictionary() as? [String:Any] {// You can now fetch the details of the document using the property name as keylet type = docObject["type"]}}
You can query for both the Document metadata and properties at the same time by using a comma separated list ofSelectResult
clauses, then each member of the response array is a Key-Value pairs as discussed earlierFor instance,consider the query
12345let searchQuery = QueryBuilder.select(SelectResult.expression(Meta.id),SelectResult.expression(Expression.property("type"))).from(DataSource.database(db)).limit(Expression.int(limit))
The results would look something like this –
123456789101112131415[{"id":"airline_1001","type": "airline"},{"id":"airline_900","type": "airline"},{"id":"hotel_1001","type": "hotel"}]
The code snippet below is an example (in Swift) of how you would process the above results to get the property values that you queried for
1234567for row in try searchQuery.execute() {if let docObject = row.toDictionary() as? [String:Any] {// You can now fetch the details of the document using the property name as keylet type = docObject["type"]let docId = docObject["id"]}}
Introducing the Where clause
Similar to SQL, you can use the where
clause to filter the documents to be returned as part of the query. The select
statement takes in an Expression
. You can chain any number of Expressions
in order to implement sophisticated filtering capabilities.
Filtering documents based on specific property
In the example below, we use Property Expression
type in conjunction with Comparison Expressions
type to filter documents based on a specific document property. The example below shows the equalTo
comparison expression.
Note: When referencing a property in a Property Expression
,we can use key paths (in dotted notation) to specify the path to a nested property.
1 2 3 4 5 |
let searchQuery = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("hotel"))) .limit(Expression.int(limit)) |
The list of supported comparison operators include
* lessThan
* notLessThan
* lessThanOrEqualTo
* notLessThanOrEqualTo
* greaterThan
* notGreaterThan
* greaterThanOrEqualTo
* notGreaterThanOrEqualTo
* equalTo
* notEqualTo
Filtering documents using logical Expressions
We can use Logical Expression
to chain together several comparison expressions
. In the example below, we fetch documents of type
hotel whose country
property is equal To “United States” or “France” and whose vacancy
property is true. In other words, we are fetching all hotels in the United States or France that have a vacancy.
1 2 3 4 5 6 7 8 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id)) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("hotel")) .and(Expression.property("country").equalTo(Expression.string ("United States")) .or(Expression.property("country").equalTo(Expression.string ("France")))) .and(Expression.property("vacancy").equalTo(Expression.boolean(true)))) .limit(Expression.int(limit)) |
Pattern Matching
The like
and regex
expressions can be used for string matching. These perform **case sensitive ** matches. So if you want to make the string matching case insensitive, you would have to use Function.lower
or Function.upper
to transform the matched string to lowercase or uppercase equivalents.
Exact Match
In the example below, we are looking for documents of type
“landmark” where the name
property exactly matches the string “Royal engineers museum”. Note that since like
does a case sensitive match, we used Function.lower
to transform matched string to lowercase equivalent. So the following query will return “landmark” type
documents with name
matching “Royal Engineers Museum”, “royal engineers museum”, “ROYAL ENGINEERS MUSEUM” and so on.
1 2 3 4 5 6 7 8 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("country")), SelectResult.expression(Expression.property("name"))) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("landmark")) .and(Function.lower(Expression.property("name")).like(Expression.string("royal engineers museum")))) .limit(Expression.int(limit)) |
Wildcard Match
We can use %
sign within a like
expression to do a wildcard match against zero or more characters. Using wildcards allows you to have some fuzziness in your search string.
In the example below, we are looking for documents of type
“landmark” where the name
property matches any string that begins with “eng” followed by zero or more characters, the letter “e”, followed by zero or more characters. Once again, we are using Function.lower
to make the search case insenstive.
The following query will return “landmark” type
documents with name
matching “Engineers”, “engine”, “english egg” , “England Eagle” and so on. Notice that the matches may span word boundaries.
1 2 3 4 5 6 7 8 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("country")), SelectResult.expression(Expression.property("name"))) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("landmark")) .and( Function.lower(Expression.property("name")).like(Expression.string("%eng%r%")))) .limit(Expression.int(limit)) |
Wildcard Character Match
We can use "_"
sign within a like
expression to do a wildcard match against a single character.
In the example below, we are looking for documents of type
“landmark” where the name
property matches any string that begins with “eng” followed by exactly 4 wildcard characters and ending in the letter “r”.
Notice that unlike the previous examples, we are not using Function.lower
to transform search string to lowercase. So the search will be case senstive.
The following query will return “landmark” type
documents with name
matching “Engineer”, “Engineer1” and so on.
1 2 3 4 5 6 7 8 9 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("country")), SelectResult.expression(Expression.property("name"))) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("landmark")) .and( Expression.property("name") .like(Expression.string("%Eng____r%")))) .limit(Expression.int(limit)) |
Regex Match
The regex
expression can be used for case sensitive matches. Similar to wildcard like
expressions, regex
expressions based pattern matching allow you to have some fuzziness in your search string.
In the example below, we are looking for documents of type
“landmark” where the name
property matches any string (on word boundaries) that begins with “eng” followed by exactly 4 wildcard characters and ending in the letter “r”. Once again, we are using Function.lower
to make the search case insenstive.
The following query will return “landmark” type
documents with name
matching “Engine”, “engine” and so on. Note that the \b
specifies that the match must occur on word boundaries.
1 2 3 4 5 6 7 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("name")) ) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("landmark")) .and(Function.lower(Expression.property("name")).regex(Expression.string("\\beng.*r.*\\b")))) .limit(Expression.int(limit)) |
Documents that have Null Or Missing Property
One of the features of the query language that sets it apart from SQL is the ability to query for documents which have null or missing properties.
The isNullOrMissing()
expression is used in conjunction with the Property Expression
to test if the specified property has a null or missing value. The isNullOrMissing()
does the reverse.
In the example below, we are looking for all documents in which the email
property that is null or missing.
1 2 3 4 5 6 |
let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("email"))) .from(DataSource.database(db)) .where(Expression.property("email").isNullOrMissing()) .limit(Expression.int(limit)) |
Ordering of Documents
It is possible to sort the results of a query based on a given expression result.
The example below returns documents of type
equal to “hotel” sorted in ascending order by the value of the title
property.
1 2 3 4 5 6 7 |
let searchQuery = QueryBuilder.select( SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("title"))) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("hotel"))) .orderBy(Ordering.property("title").ascending()) .limit(Expression.int(limit)) |
String Manipulation
String manipulation capabilities are integral to any data processing. In previous examples, we looked at how the Function.lower
could be used to transform a string to lowercase equivalent to make case insenstive string comparisons.
Couchbase Lite supports the following string processing functions.
1 2 3 4 5 6 7 8 |
- Function.lower(String Expression) - Function.ltrim(String Expression) - Function.rtrim(String Expression) - Function.trim(String Expression) - Function.upper(String Expression) - Function.length(String Expression) - Function.substring(String Expression, String Expression) |
The example below returns documents of where the email
property contains the substring “natgeo.org”. The name
property value is converted to uppercase in the response.
1 2 3 4 5 6 |
let searchQuery = QueryBuilder.select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("email")), SelectResult.expression(Expression.property("name"))) .from(DataSource.database(db)) .where(Function.contains(Expression.property("email"), substring: Expression.string ("natgeo.org"))) .limit(Expression.int(limit)) |
Collation
Collation functions enhance string comparison and sorting capabilities by supporting unicode strings, locale-aware string manipulation and language specific features like diacritics. You can learn about collation in this writeup by Jens Alfke.
The example below defines the Collation rules that the case and accents be ignored. This collator is applied to the string comparison function on the name
property. The results will include documents where name
is equal to strings strings like “Hotel Novotel Paris La Defense” , “Hotel Novotel Paris La Défense” and so on.
1 2 3 4 5 6 7 8 9 10 |
let collator = Collation.unicode() .ignoreAccents(true) .ignoreCase(true) let searchQuery = QueryBuilder.select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("name"))) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string("hotel")) .and(Expression.property("name").collate(collator).equalTo(Expression.string ("Hotel novotel paris la defense")))) .limit(Expression.int(limit)) |
There are some limitations in the Collation support – at the time of writing this post, it cannot be used with pattern matching queries such as like
and regex
.
Parameterization
One of the more powerful features of the query interface is the ability to set parameters on a query. This gives you the flexibility to update the parameter values to a query at any point without having to re-create or re-start the query. Changing the query parameters will auto re-start the query and the new parameters will take effect.
Referring to the data model above, lets suppose that you want to fetch documents where the number public-likes
is within a certain range. In the example below, we are looking for “hotel” type
documents where the umber of public-likes
is between 5 and 10.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 1. function that corresponds to the number of elements in an array let likesCount = ArrayFunction.length(Expression.property("public_likes")) // 2. Parameters for specifying lower and upper limits of range let lowerCount = Expression.parameter("lower") let upperCount = Expression.parameter("upper") // 3. Query that takes in parameters let searchQuery = QueryBuilder .select(SelectResult.expression(Meta.id), SelectResult.expression(Expression.property("name")), SelectResult.expression(likesCount).as("NumLikes") ) .from(DataSource.database(db)) .where(Expression.property("type").equalTo(Expression.string ("hotel")) .and(likesCount.between(lowerCount,and: upperCount))) .limit(Expression.int(limit)) // 4. Supplying the query parameter values let params = Parameters.init().setInt(5, forName: "lower").setInt(10, forName: "upper") searchQuery.parameters = params |
Note that the example above, you may have noticed the use of ArrayFunction
. Couchbase Lite 2.0 provides us with extensive array manipulation support. This will be discussed future in this blog post on collections.
What Next
This blog post gave you a glimpse into the powerful new Query Interface supported in Couchbase Lite 2.0. You can download the Couchbase Mobile 2.0 pre-release builds from our downloads page.
Here are other Couchbase Mobile Query related posts that may be of interest
– This blog post discusses the Full Text Search capabilities.
– This blog post discusses how to query array collections
– This blog post discusses how to do JOIN queries
If you have questions or feedback, please leave a comment below or feel free to reach out to me at Twitter @rajagp or email me priya.rajagopal@couchbase.com. The Couchbase Forums are another good place to reach out with questions.
Hi,
After i update Nuget Couchbase.Lite 2.1.2 it seems some of functionality on 1.3.1 gone like as View. here i’m attached code from 1.3.1 about getting View:
var query = Provider.Db.GetView(viewName).CreateQuery();
query.AllDocsMode = AllDocsMode.AllDocs;
How could i found on Couchbase.Lite 2.1.2 for getting VIEW?
The QueryBuilder interface supports equivalent functionality that Views did except that it provides a more intuitive way to doing it . Its SQL like format makes it simpler to use.
For specific questions around what you are looking to do and how you can do it , please post with a specific example on our dev forums at http://www.couchbase.com/forums/. Before that, I would recommend that you review our docs at https://docs.couchbase.com/couchbase-lite/2.1/csharp.html. There are several examples on how to do it in csharp.
Could you please let know how can I update document as well?
I have documents of this capacity in mobile and it has been performing badly in my current implementation(javascriptcore)
document size: 600kb
lines: 27000
total characters:614386
Kindly share a direction for my case. I can continue for any details in forums
There is a small mistake when you mention In the example,
we are looking for all documents in which the email property that is null or missing.
For that you used the wrong method notNullOrMissing() instead of isNullOrMissing().
Please update the document.
Fixed. thanks
Hi,
after i upgrade to 2.6.3, following code is not working
Function.contains(Function.lower(Expression.property(“email”)), substring: Expression.string(“Sri@xxx.com”))
.or(Function.contains(Function.lower(Expression.property(“subject”)), substring: Expression.string(“Sri@xxx.com”)))
Sorry, it was my mistake. ignore this.