couchbase/couchbase-lite-ios

Wrong type returned from query for boolean values

molo17srl opened this issue · 4 comments

Hi, using query builder i get two different behaviour using select * or select key.
If i use select * i get a boolean (__NSCFBoolean), but if i use select key i get a number (__NSCFNumber) and i can't recognize the correct type.

I wrote following sample code to reproduce the different behaviours

let mutableDoc = MutableDocument()
    .setBoolean(true, forKey: "key1")
    .setBoolean(true, forKey: "key2")
do {
    try database.saveDocument(mutableDoc)
} catch {
    fatalError("Error saving document")
}

let query = QueryBuilder
    .select(
        SelectResult.all().from("DB"),
        SelectResult.expression(Expression.property("key2").from("DB")),
        SelectResult.expression(Expression.value(true)).as("key3"),
        SelectResult.expression(Expression.boolean(true)).as("key4")
    )
    .from(DataSource.database(database).as("DB"))

let token = query.addChangeListener { (change) in
    if let results = change.results {
        results.allResults()
            .map { $0.toDictionary() }
            .forEach { doc in
                if let dict = doc["DB"] as? Dictionary<String, Any>,
                    let key1 = dict["key1"] {
                    print("DB key1 \(key1) \(type(of: key1)) \(String(cString: (key1 as AnyObject).objCType))")
                }
                if let key2 = doc["key2"] {
                    print("key2 \(key2) \(type(of: key2)) \(String(cString: (key2 as AnyObject).objCType))")
                }
                if let key3 = doc["key3"] {
                    print("key3 \(key3) \(type(of: key3)) \(String(cString: (key3 as AnyObject).objCType))")
                }
                if let key4 = doc["key4"] {
                    print("key4 \(key4) \(type(of: key4)) \(String(cString: (key4 as AnyObject).objCType))")
                }
                
                if let dict = doc["DB"] as? Dictionary<String, Any>,
                    let key1 = dict["key1"],
                    let key2 = doc["key2"] {
                    print("key1 === key2 \(key1 as AnyObject === key2 as AnyObject)")
                }
                if let dict = doc["DB"] as? Dictionary<String, Any>,
                    let DBkey2 = dict["key2"],
                    let key2 = doc["key2"] {
                    print("DBkey2 === key2 \(DBkey2 as AnyObject === key2 as AnyObject)")
                }
                if let key3 = doc["key3"], let key4 = doc["key4"] {
                    print("key3 === key4 \(key3 as AnyObject === key4 as AnyObject)")
                }
        }
    }
}

As a result of the above code i get this result

Total documents count: 1
DB key1 1 __NSCFBoolean c
key2 1 __NSCFNumber q
key3 1 __NSCFNumber q
key4 1 __NSCFNumber q
key1 === key2 false
DBkey2 === key2 false
key3 === key4 true

Why there are differents results using SelectResult.all() and SelectResult.expression(Expression.property("key2")?

Platform :

  • Device: iPhone, iPad
  • OS: iOS 13.4.1
  • Couchbase Version: CouchbaseLiteSwift 2.7.0/2.7.1 CE/EE

DictionaryProtocol can help you to fetch result in desired types.

Regarding how the internal data structure is used might not be important. Below is the recommended way to fetch data.

        let res = try! query.execute()
        for result in res.allResults() {
            let key2Result = result.boolean(forKey: "key2")
            let key3Result = result.boolean(forKey: "key3")
            let key4Result = result.boolean(forKey: "key4")
            
            let dict: DictionaryObject = result.dictionary(forKey: "DB")!
            let key1Dict = dict.boolean(forKey: "key1")
            let key2Dict = dict.boolean(forKey: "key2")

        }

Yes i know this in that way i can map the result of the query to my application model and of course in this way i'm sure to obtain the right type of the value because you cast the value of dictionary to properly type. But if there are a lot of application models it's a little bit boring manually use a typed get for each property of each model. So i prefer use the method toDictionary() that your library expose to convert the result to a dictionary and then use a decoder to decode the result to my Codable models. But using toDictionary() i found this mistakes with boolean type values.

So, why i get an integer value instead of a boolean when i convert a result to a dictionary using toDictionary() method?

Sqlite value subtypes aren't visible to the result of the query, so we can't distinguish boolean types. To make it work, we already have an open issue. You could see more details here CBL-49

pasin commented

We have fixed the boolean value projection issue but the fix will be available in v2.8.
Fixed : couchbase/couchbase-lite-core@da4791a