QueryBuilder

    +

    Description — How to use QueryBuilder to build effective queries with Couchbase Lite on Objective-C
    Related Content — Predictive Query | Live Query | Indexing

    The examples used in this topic are based on the Travel Sample app and data introduced in the Couchbase Mobile Workshop tutorial

    Introduction

    Couchbase Lite for Objective-C’s database queries are defined using the QueryBuilder API. This uses query statements of the form shown in Example 1. The structure and semantics of the query format are based on that of Couchbase’s N1QL query language.

    Example 1. Query Format
    SELECT ____ (1)
    FROM 'database' (2)
    WHERE ____, (3)
    JOIN ____ (4)
    GROUP BY ____ (5)
    ORDER BY ____ (6)
    Query Components
    1 The SELECT statement specifies the document properties that will be returned in the result set
    2 FROM specifies the database to query the documents from
    3 WHERE statement specifies the query criteria.
    The `SELECT`ed properties of documents matching this criteria will be returned in the result set
    4 JOIN statement specifies the criteria for joining multiple documents
    5 GROUP BY statement specifies the criteria used to group returned items in the result set
    6 ORDER BY statement specifies the criteria used to order the items in the result set
    We recommend working through the query section of the Couchbase Mobile Workshop as a good way to build your skills in this area.

    Indexing

    Before we begin querying documents, let’s briefly mention the importance of having a query index. A query can only be fast if there’s a pre-existing database index it can search to narrow down the set of documents to examine — see: Example 2, which shows how to create an index and our Query Troubleshooting topic.

    See the Indexing topic to learn more about indexing.
    Example 2. Creating a New Index

    This example creates a new index for the type and name properties in the Data Format shown.

    Data Format
    [
      { (1)
        "id": "hotel123",
        "type": "hotel",
        "name": "Hotel Ghia"
      },
      { (2)
        "id": "hotel456",
        "type": "hotel",
        "name": "Hotel Deluxe",
      }
    ]
    Code to Create Index
    CBLValueIndexItem *type = [CBLValueIndexItem property:@"type"];
    CBLValueIndexItem *name = [CBLValueIndexItem property:@"name"];
    CBLIndex* index = [CBLIndexBuilder valueIndexWithItems:@[type, name]];
    [database createIndex:index withName:@"TypeNameIndex" error:&error];
    Every index has to be updated whenever a document is updated, so too many indexes can hurt performance. Thus, good performance depends on designing and creating the right indexes to go along with your queries.

    SELECT statement

    Use the SELECT statement to specify which properties you want to return from the queried documents. You can opt to retrieve entire documents, or just the specific properties you need.

    Return All Properties

    Use the SelectResult.all() method to return all the properties of selected documents — see: Example 3.

    Example 3. Using SELECT to Retrieve All Properties

    This query shows how to retrieve all properties from all documents in your database.

    CBLQuery *query = [CBLQueryBuilder select:@[[CBLQuerySelectResult all]]
                                         from:[CBLQueryDataSource database:database]];

    The query.execute statement returns the results in a dictionary, where the key is the database name — see Example 4.

    Example 4. Return Data Format from SelectResult.all()
    [
      {
        "travel-sample": { (1)
          "callsign": "MILE-AIR",
          "country": "United States",
          "iata": "Q5",
          "icao": "MLA",
          "id": 10,
          "name": "40-Mile Air",
          "type": "airline"
        }
      },
      {
        "travel-sample": { (2)
          "callsign": "ALASKAN-AIR",
          "country": "United States",
          "iata": "AA",
          "icao": "AAA",
          "id": 10,
          "name": "Alaskan Airways",
          "type": "airline"
        }
      }
    ]
    1 Here we see the result for the first document matching the query criteria.
    2 Here we see the result for the next document matching the query criteria.

    See: Result Sets for more on processing query results.

    Return Selected Properties

    To access only specific properties, specify a comma separated list of SelectResult expressions, one for each property, in the select statement of your query  — see: Example 5

    Example 5. Using SELECT to Retrieve Specific Properties

    In this query we retrieve and then print the _id, type and name properties of each document.

    [data-source-url=https://github.com/couchbaselabs/docs-couchbase-lite/blob/65c8c520ded84c5e5a2324c514288eebc4e72375/modules/objc/examples/code_snippets/SampleCodeTest.m]

    The query.execute statement returns one or more key-value pairs, one for each SelectResult expression, with the property-name as the key — see Example 6

    Example 6. Select Result Format
    [
      { (1)
        "id": "hotel123",
        "type": "hotel",
        "name": "Hotel Ghia"
      },
      { (2)
        "id": "hotel456",
        "type": "hotel",
        "name": "Hotel Deluxe",
      }
    ]
    1 Here we see the result for the first document matching the query criteria.
    2 Here we see the result for the next document matching the query criteria.

    See: Result Sets for more on processing query results.

    WHERE statement

    In this section

    Comparison Operators  |   Collection Operators  |   Like Operator  |   Regex Operator  |   Deleted Document

    Like SQL, you can use the WHERE statement to choose which documents are returned by your query. The select statement takes in an Expression. You can chain any number of Expressions in order to implement sophisticated filtering capabilities.

    Comparison Operators

    The Expression Comparators can be used in the WHERE statement to specify on which property to match documents. In the example below, we use the equalTo operator to query documents where the type property equals "hotel".

    [
      { (1)
        "id": "hotel123",
        "type": "hotel",
        "name": "Hotel Ghia"
      },
      { (2)
        "id": "hotel456",
        "type": "hotel",
        "name": "Hotel Deluxe",
      }
    ]
    CBLQuery *query = [CBLQueryBuilder select:@[[CBLQuerySelectResult all]]
                                         from:[CBLQueryDataSource database:database]
                                        where:[[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"hotel"]]
                                      groupBy:nil having:nil orderBy:nil
                                        limit:[CBLQueryLimit limit:[CBLQueryExpression integer:10]]];
    
    NSEnumerator* rs = [query execute:&error];
    for (CBLQueryResult *result in rs) {
        CBLDictionary *dict = [result valueForKey:@"travel-sample"];
        NSLog(@"document name :: %@", [dict stringForKey:@"name"]);
    }

    Collection Operators

    ArrayFunction Collection Operators are useful to check if a given value is present in an array.

    CONTAINS Operator

    The following example uses the CBLQueryArrayFunction to find documents where the public_likes array property contains a value equal to "Armani Langworth".

    {
        "_id": "hotel123",
        "name": "Apple Droid",
        "public_likes": ["Armani Langworth", "Elfrieda Gutkowski", "Maureen Ruecker"]
    }
    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    CBLQuerySelectResult *name = [CBLQuerySelectResult property:@"name"];
    CBLQuerySelectResult *likes = [CBLQuerySelectResult property:@"public_likes"];
    
    CBLQueryExpression *type = [[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"hotel"]];
    CBLQueryExpression *contains = [CBLQueryArrayFunction contains:[CBLQueryExpression property:@"public_likes"]
                                                             value:[CBLQueryExpression string:@"Armani Langworth"]];
    
    CBLQuery *query = [CBLQueryBuilder select:@[id, name, likes]
                                         from:[CBLQueryDataSource database:database]
                                        where:[type andExpression: contains]];
    
    NSEnumerator* rs = [query execute:&error];
    for (CBLQueryResult *result in rs) {
        NSLog(@"public_likes :: %@", [[result arrayForKey:@"public_likes"] toArray]);
    }

    IN Operator

    The IN operator is useful when you need to explicitly list out the values to test against. The following example looks for documents whose first, last or username property value equals "Armani".

    NSArray *values = @[[CBLQueryExpression property:@"first"],
                       [CBLQueryExpression property:@"last"],
                       [CBLQueryExpression property:@"username"]];
    
    [CBLQueryBuilder select:@[[CBLQuerySelectResult all]]
                       from:[CBLQueryDataSource database:database]
                      where:[[CBLQueryExpression string:@"Armani"] in:values]];

    Like Operator

    In this section

    String Matching  |   Wildcard Match  |   Wildcard Character Match

    String Matching

    The Like() operator can be used for string matching.

    The like operator performs case sensitive matches.
    To perform case insensitive matching, use Function.lower or Function.upper to ensure all comparators have the same case, thereby removing the case issue.

    Example 7. Case-insensitive Matching

    This query returns landmark type documents where the name matches the string "Royal Engineers Museum", regardless of how it is capitalized (so, it selects "royal engineers museum", "ROYAL ENGINEERS MUSEUM" and so on).

    Note the use of Function.lower to transform name values to the same case as the literal comparator.

    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    CBLQuerySelectResult *country = [CBLQuerySelectResult property:@"country"];
    CBLQuerySelectResult *name = [CBLQuerySelectResult property:@"name"];
    
    CBLQueryExpression *type = [[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"landmark"]];
    CBLQueryExpression *like = [[CBLQueryFunction lower:[CBLQueryExpression property:@"name"]] like:[CBLQueryExpression string:@"royal engineers museum"]];
    
    CBLQuery *query = [CBLQueryBuilder select:@[id, country, name]
                                         from:[CBLQueryDataSource database:database]
                                        where:[type andExpression: like]];
    
    NSEnumerator* rs = [query execute:&error];
    for (CBLQueryResult *result in rs) {
        NSLog(@"name property :: %@", [result stringForKey:@"name"]);
    }

    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 insensitive.

    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.

    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    CBLQuerySelectResult *country = [CBLQuerySelectResult property:@"country"];
    CBLQuerySelectResult *name = [CBLQuerySelectResult property:@"name"];
    
    CBLQueryExpression *type = [[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"landmark"]];
    CBLQueryExpression *like = [[CBLQueryFunction lower:[CBLQueryExpression property:@"name"]] like:[CBLQueryExpression string:@"eng%e%"]];
    
    CBLQueryLimit *limit = [CBLQueryLimit limit:[CBLQueryExpression integer:10]];
    
    CBLQuery *query = [CBLQueryBuilder select:@[id, country, name]
                                         from:[CBLQueryDataSource database:database]
                                        where:[type andExpression: like]
                                      groupBy:nil having:nil orderBy:nil
                                        limit:limit];

    Wildcard Character Match

    We can use an _ 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". The following query will return "landmark" type documents with the name matching "Engineer", "engineer" and so on.

    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    CBLQuerySelectResult *country = [CBLQuerySelectResult property:@"country"];
    CBLQuerySelectResult *name = [CBLQuerySelectResult property:@"name"];
    
    CBLQueryExpression *type = [[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"landmark"]];
    CBLQueryExpression *like = [[CBLQueryExpression property:@"name"] like:[CBLQueryExpression string:@"eng____r"]];
    
    CBLQueryLimit *limit = [CBLQueryLimit limit:[CBLQueryExpression integer:10]];
    
    CBLQuery *query = [CBLQueryBuilder select:@[id, country, name]
                                         from:[CBLQueryDataSource database:database]
                                        where:[type andExpression: like]
                                      groupBy:nil having:nil orderBy:nil
                                        limit:limit];

    Regex Operator

    Similar to the wildcards in like expressions, regex based pattern matching allow you to introduce an element of fuzziness in your search string.

    Note though, that the regex operator is case sensitive.

    For more on the regex spec used by Couchbase Lite see cplusplus regex reference page

    The code shown in Example 8 executes a query that will return documents of type "landmark" with a name matching "Engine", "engine" and so on.

    Example 8. Using Regular Expressions

    This example returns documents with a type of "landmark" and a name property that matches any string that begins with "eng" and ends in the letter "e".

    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    CBLQuerySelectResult *name = [CBLQuerySelectResult property:@"name"];
    
    CBLQueryExpression *type = [[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"landmark"]];
    CBLQueryExpression *regex = [[CBLQueryExpression property:@"name"] regex:[CBLQueryExpression string:@"\\bEng.*e\\b"]];
    
    CBLQueryLimit *limit = [CBLQueryLimit limit:[CBLQueryExpression integer:10]];
    
    CBLQuery *query = [CBLQueryBuilder select:@[id, name]
                                         from:[CBLQueryDataSource database:database]
                                        where:[type andExpression: regex]
                                      groupBy:nil having:nil orderBy:nil
                                        limit:limit];
    1 The \b specifies that the match must occur on word boundaries.

    Deleted Document

    You can query documents that have been deleted (tombstones) [1].

    Example 9. Query to select Deleted Documents

    This example shows how to query deleted documents in the database. The result set it returns is an array of key-value pairs. One for each document matching the criteria — see Select Document Id Only for how to work with this result set.

    // Query documents that have been deleted
    CBLQuery* query = [CBLQueryBuilder select: @[[CBLQuerySelectResult expression:CBLQueryMeta.id]]
                                         from: [CBLQueryDataSource database:database]
                                        where: CBLQueryMeta.isDeleted];

    JOIN statement

    The JOIN clause enables you to select data from multiple documents that have been linked by criteria specified in the JOIN statement. For example to combine airline details with route details, linked by the airline id — see Example 10.

    Example 10. Using JOIN to Combine Document Details

    This example JOINS the document of type route with documents of type airline using the document ID (id) on the _airline document and airlineid on the route document.

    CBLQuerySelectResult *name = [CBLQuerySelectResult expression:[CBLQueryExpression property:@"name" from:@"airline"]];
    CBLQuerySelectResult *callsign = [CBLQuerySelectResult expression:[CBLQueryExpression property:@"callsign" from:@"airline"]];
    CBLQuerySelectResult *dest = [CBLQuerySelectResult expression:[CBLQueryExpression property:@"destinationairport" from:@"route"]];
    CBLQuerySelectResult *stops = [CBLQuerySelectResult expression:[CBLQueryExpression property:@"stops" from:@"route"]];
    CBLQuerySelectResult *airline = [CBLQuerySelectResult expression:[CBLQueryExpression property:@"airline" from:@"route"]];
    
    CBLQueryJoin *join = [CBLQueryJoin join:[CBLQueryDataSource database:database as:@"route"]
                                         on:[[CBLQueryMeta idFrom:@"airline"] equalTo:[CBLQueryExpression property:@"airlineid" from:@"route"]]];
    
    CBLQueryExpression *typeRoute = [[CBLQueryExpression property:@"type" from:@"route"] equalTo:[CBLQueryExpression string:@"route"]];
    CBLQueryExpression *typeAirline = [[CBLQueryExpression property:@"type" from:@"airline"] equalTo:[CBLQueryExpression string:@"airline"]];
    CBLQueryExpression *sourceRIX = [[CBLQueryExpression property:@"sourceairport" from:@"route"] equalTo:[CBLQueryExpression string:@"RIX"]];
    
    CBLQuery *query = [CBLQueryBuilder select:@[name, callsign, dest, stops, airline]
                                         from:[CBLQueryDataSource database:database as:@"airline"]
                                         join:@[join]
                                        where:[[typeRoute andExpression:typeAirline] andExpression:sourceRIX]];

    GROUP BY statement

    You can perform further processing on the data in your result set before the final projection is generated. The following example looks for the number of airports at an altitude of 300 ft or higher and groups the results by country and timezone.

    Data Model for Example
    {
        "_id": "airport123",
        "type": "airport",
        "country": "United States",
        "geo": { "alt": 456 },
        "tz": "America/Anchorage"
    }
    Example 11. Query using GroupBy

    This example shows a query that selects all airports with an altitude above 300ft. The output (a count, $1) is grouped by country, within timezone.

    CBLQuerySelectResult *count = [CBLQuerySelectResult expression:[CBLQueryFunction count:[CBLQueryExpression all]]];
    CBLQuerySelectResult *country = [CBLQuerySelectResult property:@"country"];
    CBLQuerySelectResult *tz = [CBLQuerySelectResult property:@"tz"];
    
    CBLQueryExpression *type = [[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"airport"]];
    CBLQueryExpression *geoAlt = [[CBLQueryExpression property:@"geo.alt"] greaterThanOrEqualTo:[CBLQueryExpression integer:300]];
    
    CBLQuery *query = [CBLQueryBuilder select:@[count, country, tz]
                                         from:[CBLQueryDataSource database:database]
                                        where:[type andExpression: geoAlt]
                                      groupBy:@[[CBLQueryExpression property:@"country"],
                                                [CBLQueryExpression property:@"tz"]]];

    The query shown in Example 11 generates the following output:

    There are 138 airports on the Europe/Paris timezone located in France and above 300 ft
    There are 29 airports on the Europe/London timezone located in United Kingdom and above 300 ft
    There are 50 airports on the America/Anchorage timezone located in United States and above 300 ft
    There are 279 airports on the America/Chicago timezone located in United States and above 300 ft
    There are 123 airports on the America/Denver timezone located in United States and above 300 ft

    ORDER BY statement

    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.

    Example 12. Query using OrderBy
    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    CBLQuerySelectResult *title = [CBLQuerySelectResult property:@"title"];
    
    CBLQuery *query = [CBLQueryBuilder select:@[id, title]
                                         from:[CBLQueryDataSource database:database]
                                        where:[[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"hotel"]]
                                      orderBy:@[[[CBLQueryOrdering property:@"title"] descending]]];

    The query shown in Example 12 generates the following output:

    Aberdyfi
    Achiltibuie
    Altrincham
    Ambleside
    Annan
    Ardèche
    Armagh
    Avignon

    Date/Time Functions

    Couchbase Lite documents support a date type that internally stores dates in ISO 8601 with the GMT/UTC timezone.

    Couchbase Lite 2.5 adds the ability to run date comparisons in your Couchbase Lite queries. To do so, four functions have been added to the Query Builder API:

    Function.StringToMillis(Expression.Property("date_time"))

    The input to this will be a validly formatted ISO 8601 date_time string. The end result will be an expression (with a numeric content) that can be further input into the query builder.

    Function.StringToUTC(Expression.Property("date_time"))

    The input to this will be a validly formatted ISO 8601 date_time string. The end result will be an expression (with string content) that can be further input into the query builder.

    Function.MillisToString(Expression.Property("date_time"))

    The input for this is a numeric value representing milliseconds since the Unix epoch. The end result will be an expression (with string content representing the date and time as an ISO 8601 string in the device’s timezone) that can be further input into the query builder.

    Function.MillisToUTC(Expression.Property("date_time"))

    The input for this is a numeric value representing milliseconds since the Unix epoch. The end result will be an expression (with string content representing the date and time as a UTC ISO 8601 string) that can be further input into the query builder.

    Result Sets

    Processing

    This section shows how to handle the returned result sets for different types of SELECT statements.

    The result set format and its handling varies slightly depending on the type of SelectResult statements used. The result set formats you may encounter include those generated by :

    To process the results of a query, you first need to execute it using Query.execute.

    The execution of a Couchbase Lite for Objective-C’s database query typically returns an array of results, a result set.

    • The result set of an aggregate, count-only, query is a key-value pair — see Select Count-only — which you can access using the count name as its key.

    • The result set of a query returning document properties is an array.
      Each array row represents the data from a document that matched your search criteria (the WHERE statements) The composition of each row is determined by the combination of SelectResult expressions provided in the SELECT statement. To unpack these result sets you need to iterate this array.

    Select All Properties

    Query

    The Select statement for this type of query, which returns all document properties for each document matching the query criteria, is fairly straightforward — see Example 13

    Example 13. Query selecting All Properties
    CBLDatabase *db = [[CBLDatabase alloc] initWithName:@"hotels" error: &error];
    
    CBLQuery *listQuery;
    
    *listQuery = [CBLQueryBuilder select:@[[CBLQuerySelectResult all]]
                 from:[CBLQueryDataSource database:db]] (1)
    Result Set Format

    The result set returned by queries using SelectResult.all is an array of dictionary objects — one for each document matching the query criteria.

    For each result object, the key is the database name and the 'value' is a dictionary representing each document property as a key-value pair — see: Example 14.

    Example 14. Format of Result Set (All Properties)
    [
      {
        "travel-sample": { (1)
          "callsign": "MILE-AIR",
          "country": "United States",
          "iata": "Q5",
          "icao": "MLA",
          "id": 10,
          "name": "40-Mile Air",
          "type": "airline"
        }
      },
      {
        "travel-sample": { (2)
          "callsign": "ALASKAN-AIR",
          "country": "United States",
          "iata": "AA",
          "icao": "AAA",
          "id": 10,
          "name": "Alaskan Airways",
          "type": "airline"
        }
      }
    ]
    1 Here we see the result for the first document matching the query criteria.
    2 Here we see the result for the next document matching the query criteria.
    Result Set Access

    In this case access the retrieved document properties by converting each row’s value, in turn, to a dictionary — as shown in Example 15.

    Example 15. Using Document Properties (All)
    NSMutableArray* matches = [[NSMutableArray alloc] init]; // add to native dictionary
        CBLQueryResultSet* resultset = [listQuery execute:&error];
    
        for (CBLQueryResult *result in resultset.allResults) { // access the resultSet.allResults
    
            CBLDictionary *match = [result valueAtIndex: 0];
    
            [matches addObject: [match toDictionary]];
    
            NSLog(@"id = %@", [match stringForKey:@"id"]);
            NSLog(@"name = %@", [match stringForKey:@"name"]);
            NSLog(@"type = %@", [match stringForKey:@"type"]);
            NSLog(@"city = %@", [match stringForKey:@"city"]);
        } // end for
    1 Here we get the dictionary of document properties using the database name as the key. You can add this dictionary to an array of returned matches, for processing elsewhere in the app.
    2 Alternatively you can access the document properties here, by using the property names as keys to the dictionary object.

    Select Specific Properties

    Query

    Here we use SelectResult.expression(property("<property-name>"))) to specify the document properties we want our query to return — see: Example 16.

    Example 16. Query selecting Specific Properties
    CBLDatabase *db = [[CBLDatabase alloc] initWithName:@"hotels" error: &error];
    
    CBLQuery *listQuery;
    
    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    
    CBLQuerySelectResult *type = [CBLQuerySelectResult property:@"type"];
    
    CBLQuerySelectResult *name = [CBLQuerySelectResult property:@"name"];
    
    CBLQuerySelectResult *city = [CBLQuerySelectResult property:@"city"];
    
    *listQuery = [CBLQueryBuilder select:@[id, type, name, city]
                 from:[CBLQueryDataSource database:db]] (1)
    Result Set Format

    The result set returned when selecting only specific document properties is an array of dictionary objects — one for each document matching the query criteria.

    Each result object comprises a key-value pair for each selected document property — see Example 17

    Example 17. Format of Result Set (Specific Properties)
    [
      { (1)
        "id": "hotel123",
        "type": "hotel",
        "name": "Hotel Ghia"
      },
      { (2)
        "id": "hotel456",
        "type": "hotel",
        "name": "Hotel Deluxe",
      }
    ]
    1 Here we see the result for the first document matching the query criteria.
    2 Here we see the result for the next document matching the query criteria.
    Result Set Access

    Access the retrieved properties by converting each row into a dictionary — as shown in Example 18.

    Example 18. Using Returned Document Properties (Specific Properties)
        NSMutableArray* matches = [[NSMutableArray alloc] init]; // save to native array
    
        CBLQueryResultSet* resultset = [listQuery execute:&error];
    
        for (CBLQueryResult *result in resultset.allResults) { // all results
    
            [matches addObject: [result toDictionary]];
    
            NSLog(@"id = %@", [result stringForKey:@"id"]);
            NSLog(@"name = %@", [result stringForKey:@"name"]);
            NSLog(@"type = %@", [result stringForKey:@"type"]);
            NSLog(@"city = %@", [result stringForKey:@"city"]);
    
        } // end for

    Select Document Id Only

    Query

    You would typically use this type of query if retrieval of document properties directly would consume excessive amounts of memory and-or processing time — see: Example 19.

    Example 19. Query selecting only Doc Id
    CBLDatabase *db = [[CBLDatabase alloc] initWithName:@"hotels" error: &error];
    
    CBLQuery *listQuery;
    
    CBLQuerySelectResult *id = [CBLQuerySelectResult expression:[CBLQueryMeta id]];
    
    *listQuery = [CBLQueryBuilder select:@[id]
                 from:[CBLQueryDataSource database:db]]
    Result Set Format

    The result set returned by queries using a SelectResult expression of the form SelectResult.expression(meta.id) is an array of dictionary objects — one for each document matching the query criteria. Each result object has id as the key and the ID value as its value — -see Example 20.

    Example 20. Format of Result Set (Doc Id only)
    [
      {
        "id": "hotel123"
      },
      {
        "id": "hotel456"
      },
    ]
    Result Set Access

    In this case, access the required document’s properties by unpacking the id and using it to get the document from the database — see: Example 21.

    Example 21. Using Returned Document Properties (Document Id)
    CBLDictionary *match;
    
    CBLMutableArray* matches = [[CBLMutableArray alloc] init];
    
    CBLQueryResultSet* resultset = [listQuery execute:&error];
    
    for (CBLQueryResult *result in resultset) {
    
      *match = [result toDictionary];
    
      *thisDocsId = [match stringForKey:@"id"] (1)
    
      // Now you can get the document using its ID
      // for example using
      CBLMutableDocument* thisDoc =
        [thisDB documentWithID: thisDocsId]
    
    } // end for
    1 Extract the Id value from the dictionary and use it to get the document from the database

    Select Count-only

    Example 22. Query selecting a Count-only
    CBLDatabase *db = [[CBLDatabase alloc] initWithName:@"hotels" error: &error];
    
    CBLQuerySelectResult *count =
      [CBLQuerySelectResult expression:[CBLQueryFunction count:   [CBLQueryExpression all]]];
    
    *listQuery = [CBLQueryBuilder select:@[count]
                 from:[CBLQueryDataSource database:db]] (1)
    1 The alias name, mycount, is used to access the count value.
    Result Set Format

    The result set returned by a count such as Select.expression(Function.count(Expression.all))) is a key-value pair. The key is the count name, as defined using SelectResult.as — see: Example 23 for the format and Example 22 for the query.

    Example 23. Format of Result Set (Count)
    {
      "mycount": 6
    }
    1 Here we see the key-value pair returned by a count.
    Result Set Access

    Access the count using its alias name (mycount in this example) — see Example 24

    Example 24. Using Returned Document Properties (Count)
    CBLDictionary *match;
    
    CBLMutableArray* matches = [[CBLMutableArray alloc] init];
    
    CBLQueryResultSet* resultset = [listQuery execute:&error];
    
    for (CBLQueryResult *result in resultset) {
    
      *match = [result toDictionary];
    
      *thisCount = [match intForKey:@"mycount"] (1)
    
    } // end for
    1 Get the count using the SelectResult.as alias, which is used as its key.

    Handling Pagination

    One way to handle pagination in high-volume queries is to retrieve the results in batches. Use the limit and offset feature, to return a defined number of results starting from a given offset — see: Example 25.

    Example 25. Query Pagination
    int thisOffset = 0;
    int thisLimit = 20;
    CBLDatabase *db = [[CBLDatabase alloc] initWithName:@"hotels" error: &error];
    
    CBLQuery* listQuery =
                [CBLQueryBuilder
                    select: @[[CBLQuerySelectResult all]]
                    from: [CBLQueryDataSource database: db]
                    limit: [CBLQueryLimit
                                limit: [CBLQueryExpression integer: thisLimit]
                                offset: [CBLQueryExpression integer: thisOffset]]
                ];
    1 Return a maximum of limit results starting from result number offset
    For more on using the QueryBuilder API, see our blog: Introducing the Query Interface in Couchbase Mobile

    1. Starting in Couchbase Lite 2.5