Plans for GRDB and Swift 5


(Gwendal Roué) #1

Hello, here are a few notes about the consequences of the forecoming release of Swift 5 on GRDB. Your comments are welcome.

First, some context. The current GRDB version is 3.6.2. All GRDB 3 versions support all Swift compilers in all modes, from 4.0 (Xcode 9.3) to 4.2 (Xcode 10.1). Due to the constraints of semantic versionning, those compatibilies were never broken since GRDB 3.0.0.

Swift 5 supports three modes (-swift-version flags): 4, 4.2, and 5. It is thus, theorically, able to provide the same level of compatibility.

However, until Xcode 10, Swift was lacking SE-0212, shipped with Swift 4.2.

Without SE-0212, it becomes too difficult to maintain GRDB in the long run:

// Breaks with Swift 5 compiler. Intent is: #if compiler(>= 4.2)
#if swift(>=4.1.50) || (swift(>=3.4) && !swift(>=4.0))
    ...
#else
...
#endif

// Warning with Swift 5 compiler in Swift 4 mode:
// > 'Hashable.hashValue' is deprecated as a protocol requirement;
// > conform type 'TableAlias' to 'Hashable' by implementing 'hash(into:)' instead
#if swift(>=4.2)
func hash(into hasher: inout Hasher) { ... }
#else
var hashValue: Int { ... }
#endif

Conclusions

  • I plan to ship a new major release, GRDB 4, which will only support the Swift 4.2+ modes, and will require Xcode 10+.

    This major release will contain other light breaking changes. iOS 8 will no longer be supported. Deprecated methods will be removed. Nothing major expected at this point.

    Yet I want to explore @brentdax's ideas around the new String interpolation API, in order to see if it can help writing safe SQL (see those tweets). I don't know yet what will be the consequences of this exploration.

  • I will greatly appreciate any pull request that brings Swift 5 support for GRDB 3. If this pull request never comes, GRDB 3 will not support Swift 5, and the first GRDB version that supports Swift 5 will be GRDB 4. Please reply below if you are interested.

If you have any concern, question, good ideas about other useful breaking changes, it is time to raise your voice!

Happy GRDB!


(Michael Hanna) #2

Thanks for staying on top of GRDB! So I should be able to link GRDB 4 with an iOS 10 app, assuming I compile with Swift 5? I can confirm that I cannot compile GRDB 3.6.2 using Swift 5/Xcode 10.2b2


(Gwendal Roué) #3

Thanks ;-)

Yes. And if for some reason you are stuck with Xcode 10.1, you will also be able to link GRDB 4 with an iOS 10 app.

Yes. I expect the next beta to contain the necessary fix. Meanwhile, you can use the latest Swift 5 snapshot!


(Michael Hanna) #4

Good to know!


(Gwendal Roué) #5

Anyone interested in this topic can check the Swift 5: SQL interpolation pull request. It is still in the draft status: comments are welcome.


(Gwendal Roué) #6

:rocket: SQL Interpolation has been merged in the GRDB-4.0 branch! The feature is fully documented.

As usual, writing documentation generates a virtuous feedback loop on the API. It works great, looks great, and is interestingly more able, sometimes, than annotation-based APIs found in other languages (I think pretty much about the Room persistence library by Google). Look especially at the interaction between the maximumScore() and leaders() requests below. Isn't it beautiful?

struct Player {
    var id: Int64
    var name: String
    var score: Int?
}

extension Player: Decodable, FetchableRecord, TableRecord {
    /// Deletes all player with no score
    static func deleteAllWithoutScore(_ db: Database) throws {
        try db.execute(literal: "DELETE FROM \(self) WHERE \(CodingKeys.score) IS NULL")
    }
    
    /// The player with a given id
    static func filter(id: Int64) -> SQLRequest<Player> {
        return "SELECT * FROM \(self) WHERE \(CodingKeys.id) = \(id)"
    }
    
    /// All players with the given ids
    static func filter(ids: [Int64]) -> SQLRequest<Player> {
        return "SELECT * FROM \(self) WHERE \(CodingKeys.id) IN \(ids)"
    }
    
    /// The maximum score
    static func maximumScore() -> SQLRequest<Int> {
        return "SELECT MAX(\(CodingKeys.score)) FROM \(self)"
    }
    
    /// All players whose score is the maximum score
    static func leaders() -> SQLRequest<Player> {
        return """
            SELECT * FROM \(self)
            WHERE \(CodingKeys.score) = \(maximumScore())
            """
    }
}

More information? SQL Interpolation

Many, many thanks to @brentdax and @Michael_Ilseman for SE-0228 :clap: