stephencelis / SQLite.swift

A type-safe, Swift-language layer over SQLite3.
MIT License
9.72k stars 1.57k forks source link

Fatal error when using scalar #339

Closed Silve2611 closed 7 years ago

Silve2611 commented 8 years ago

I am encountering an error when using db.scalar

fatal error: 'try!' expression unexpectedly raised an error: database is locked: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.1.101.15/src/swift/stdlib/public/core/ErrorType.swift, line 50

It happens because it acceses a database that is also accesed from another application. When I what to use try with the scalar Xcode tells me that no error will be thrown.

I am now using a select all and making the count programatically and it doesn't crash anymore. I also have a busyhandler.

Is it recommended using scalar?

mikemee commented 8 years ago

@stephencelis IM(NS)HO, this is another example of a scenario where trying to trap/hide the error for users removes control from the programmer. Like #305 and #259. As these suggest, unfortunately, databases are complicated enough that an error can happen pretty much at any time. Multi-user / multi-threaded pretty much guarantees that.

Your baby, your call, but I'd suggest that as a guiding principle, it's vital for programmers to able to control if their app crashes, and with the Swift 2 error handling available, we probably should use it around any call to the database and surface it.

Otherwise, people will subvert the typesafe features and just go with prepare or run with embedded SQL statements just so they can be sure of catching any error.

If I seem a little passionate about it, I've been down this route of hiding complexity from users before (when I designed DAO for VB / Access 1), and ... let's say I learned the hard way that it didn't work so well. If programmers can't stomach the heat (and add error handling), they shouldn't be in the kitchen. Databases are amazing tools, but you have to understand how they work or they bite in (foot?) when you least expect it (and of course then it gets blamed on the tool / library).

stephencelis commented 8 years ago

Yeah. It seems like we can't make the guarantees I'd like to make :/ Perhaps we need to expose the interfaces as throws and avoid calling try! wherever we can't guarantee it's merely programmer error.

mikemee commented 8 years ago

Yes. I think that's prudent. I wish I had a better idea, but better to map reality.

nickmshelley commented 8 years ago

One middle ground option you may want to consider is to make some methods return optionals instead of throwing. The advantage is that you save some do/try/catch complexity (at least I find error handling more complex than optional handling), but the disadvantage is that you only know that something failed, not specifically what.

Personally, I don't find it useful to show users specific errors, and if I want more specific errors in a reproducible case (usually when I'm implementing something for the first time) I can dig into the source and find out myself what went wrong. So I tend to prefer optional APIs as opposed to throws.

Silve2611 commented 8 years ago

I understand you point. The thing ist that the error does not appear when i am upackaging the value but when i am trying to receive it. So the error occurs on the access on the database. If it is locked it crashed. How would optionals save me from that?

nickmshelley commented 8 years ago

Sorry @Silve2611, my comment was directed towards the discussion between @stephencelis and @mikemee about exposing possible errors for the developer to handle instead of just crashing. I was suggesting an optional return value as an alternative to a throwing function.

mikemee commented 8 years ago

@nickmshelley Great comments. I think it's too early to see if there will be a consensus for Swift and which way it will go. I too generally prefer optionals, but with databases (and file i/o and network i/o), I like to be able to get the exact error and log it and then silently continue on if possible. Then I can gather the errors from the wild and later look for patterns and ways to improve the code. E.g. I can catch specific errors and retry depending on the error code, or quietly move on, or ... whatever seems best.

If it is just an error value, I'm stuck. Then I don't really know why or how to improve. The wrapper, in this case SQLite.swift knows what's going on, but unless there's another error function or method or something I can call for more info (which may not be robust in multi-threaded situations), I'm stuck.

And, of course, it's possible to write a wrapper function to convert try to return optionals if that's preferred. But it's not possible to do the opposite.

Btw, I found this article to be interesting and relevant to this discussion: http://www.sunsetlakesoftware.com/2015/06/12/swift-2-error-handling-practice

Silve2611 commented 8 years ago

I had to adjust my code. I guess it is working now. The Problem is, that multiple programs access the same database. So if some program is accessing the database it should not crash. The try! is producing that. I'm trying this now.

func settingsChange() -> Int64{
    do{
        let stmt = try db.prepare("SELECT COUNT(*) as count   FROM TProperties WHERE indicator IS NOT NULL")
        for row in stmt{
            if let result = row[0]{
                return result as! Int64
            }
        }
        print(stmt)
    }catch{
        print("settingsChange failed")
    }
    return -1;
}
fireflyexperience commented 8 years ago

100% agree with what has been said here about ensuring the power stays with the programmer to do something about errors. IMO the scalar methods should all be marked "throws"

Example; we found a situation where the app is trying to count the number of items in a table when the table may not have been created yet. in this particular case, it doesn't matter that the query fails to fetch the count; but crashing the app definitely does matter. Given this situation we have to use the work around listed above by Silve2611.

Generally my view is throw rather than abort then let the app developer figure out what the appropriate behaviour is; If the issue is less serious, then optionals are fine.

ghost commented 8 years ago

Please, I am going through the same situation. It is too difficult to use SQLite without the actual error messages - hours and hours trying to figure out what is wrong. Any tips? Thanks.

Silve2611 commented 8 years ago

You are propably encountering the same error as i did. I ended up building the scala function myself in Swift.

func getSettings() ->(Array<Row>){ let table = Table("TableName") var all = [Row]() do{ all = Array(try db.prepare(table)) }catch{ print("getSettings failed") } return(all) } Now you can iterate over all elements and make the count by yourself. Not the best solution but it is not crashing.

jberkel commented 7 years ago

scalar(...) is marked as throws, closing