IOS/Swift: using query.keys to chain queries

(First post, as an enthusiastic new CBL developer on IOS…)

I am on the fairly recent Xcode (7.3.1), starting to use CBL couchbase-lite-ios-community_1.2.1-13.

Currently I am trying to understand how to use the ability to restrict a query by setting the .keys property.
My understanding of this feature is that CBL should consider only documents whose documentID is in the array that I am passing to the query.

So I am running unit tests to get my ‘CBL JSON’ caching object to do what I need.

I have a set of ‘Locations’ (JSON documents with ID style ‘Location-1’, …, 'Location-), that are indexed.

The method that runs the query does not put anything in the .keys query property, be default.
So the result I would expect in the code shown here under is to first get all 15 locations, as checked by the first XCTAssert.

On the second run, the only difference is that I do set 4 location keys in the keys property. The same query is run, followed by the same ‘enumerator walking’ method that just adds all found keys in an array

This array the test receives is empty; while I would expect the 4 locations to be the ones I do find (see the copied debug ‘trace’)…

What I am doing wrong?



// ********************************************
func test_GetAllLocations() {
    var resultsArray: NSArray?
    let cacheManager = INAJSONCacheManager.sharedInstance
    var resultSet = cacheManager.listLocationsByType(.All)
    if let results = resultSet.result {
        resultsArray = cacheManager.traverseEnumeratorForKeysArray(results)
        XCTAssert(resultsArray?.count == 15 , "Could not find the expected number of locations!!")
        print("Found locations (15):\n\(resultsArray!)")
    resultSet = cacheManager.listLocationsByType(.All, asChildSetOfParentSetOfKeys: ["Location-1", "Location-2", "Location-3", "Location-10"])
    if let results = resultSet.result {
        resultsArray = cacheManager.traverseEnumeratorForKeysArray(results)
        XCTAssert(resultsArray?.count == 4 , "Could not find the expected number of locations!!")
        print("Found locations (4):\n\(resultsArray!)")

// ********************************************
func listLocationsByType(typeKey: LocationSelectorType, asChildSetOfParentSetOfKeys parentKeys: [AnyObject]? = nil) -> (result: CBLQueryEnumerator?, error: ErrorType?) {
    var query: CBLQuery?
    switch typeKey {
    case .All:
        query = self.database?.viewNamed(kLocationsIndex).createQuery()

    case .Main:
        query = self.database?.viewNamed(kMainLocationsIndex).createQuery()
    case .NonMain:
        query = self.database?.viewNamed(kNonMainLocationsIndex).createQuery()
    case .SubLoc:
        query = self.database?.viewNamed(kSubLocLocationsIndex).createQuery()

    query?.mapOnly = true
    if let parentKeys = parentKeys {
        query?.keys = parentKeys

    var result: CBLQueryEnumerator?
    var error: ErrorType?
    if let locQuery = query {
        do {
            result = try
        } catch let locQueryError {
            error = locQueryError
    let resultSet = (result: result, error: error)
    return resultSet

// ********************************************

Test Case ‘-[Test_App.LocationsSearchTests test_GetAllLocations]’ started.
Found locations (15):
Found locations (4):
Test Case ‘-[Test_App.LocationsSearchTests test_GetAllLocations]’ failed (0.018 seconds).

Not exactly. Setting the keys property says that you want to fetch all rows of the index whose key is contained in that list of keys.

A key is not a document ID. It’s the first parameter to emit as called by the map function. Basically the emit function adds key/value pairs to the view’s index, and queries fetch rows from the index based on their keys; either a key range (using startKey and/or endKey) or a set of keys (using keys).

You didn’t show the map functions of the views, so I don’t know what your keys look like.

Hi Jens,

the difference is of course big enough! I should have thought of the fact that ‘key’ has to refer to the key of the index and not the documentID… Quite logical, after all.

This is the map function:

func createAllLocationsView() {
    if let database = self.database {
        let locationsTypeView = database.viewNamed(kLocationsIndex)
        locationsTypeView.setMapBlock({(doc, emit) in
            if let docType = doc[kDocumentType] as? String {
                if docType == kLocationDocument {
                    let locationName = doc["name"] as! String
                    emit(locationName, nil)
            }, reduceBlock: { (keys, values, rereduce) in
                if rereduce {
                    return CBLView.totalValues(values)
                } else {
                    return values.count
            }, version: "1")

I can thus limit the set of locations to be found using their ‘name’ values inside the array I do inject in the query. Right?


Or (better for query chaining) rework the emit() lines to have the unique documentID as their key value… So it can be shared through all the queries that can be meaningfully chained!

The document ID is already part of every index row; you don’t need to emit it yourself. You can get it from CBLQueryRow.documentID.

Generally the key is the attribute you want to search for or sort by. If you only want to search on document IDs, just use an allDocs query instead of creating a view.