Announcing SwiftKuery 3.0

By Matt Kilner

Created on 2018-12-18

We have recently released version 3.0 of Swift-Kuery along with version 2.0 of Swift-Kuery-PostgreSQL, SwiftKueryMySQL and Swift-Kuery-SQLite. You can find a summary of the major changes below, for more detail on the changes please refer to the Swift-Kuery migration guide.

API changes

The SwiftKuery API has had an overhaul and has been updated to be entirely asynchronous.

You now pass callbacks to the API for establishing your connections and preparing statements in the same way as you do currently when executing your queries and transactions.

The API for retrieving results from the databases has also been updated to be asynchronous in style and you now pass a callback into the asRows function. A new API forEach has been added to allow iteration of the result set.


Asynchronous API behaviour

Previously the SwiftKuery API, while largely asynchronous in style, had underlying behaviour which was synchronous. In the SwiftKuery 3.0 and plugin updates the behaviour of the API has been reimplemented to be fully asynchronous.

With this change all code using the SwiftKuery API needs to be written in an asynchronous style. For example if you previously had code that did not nest API calls within the previous calls completion handler, such as the example below, you would now see undefined behaviour as both execute calls could run in parallel:

// BAD CODE
let query = Select(from: myTable)
connection.execute(query: query) { result in
    //Handle result
}
let newQuery = Select(from: otherTable)
connection.execute(query: newQuery) { result in
    //Handle result
}

For this to work in an asynchronous environment subsequent tasks must be chained in the preceding task’s completion handler, for example:

// CORRECT CODE
let query = Select(from: myTable)
connection.execute(query: query) { result in
    //Handle result
    let newQuery = Select(from: otherTable)
    connection.execute(query: newQuery) { result in
        //Handle result
    }
}

Reuse table definitions

We have added the ability to specify a name for the table created from your structs. This will minimise duplication of code when wanting to use several tables with the same definition. For example the following could be used to represent both a Customer and an Employeee:

class Person: Table {
    let forename = Column("forename", String.self)
    let surname = Column("surname", String.self)
    let address = Column("address", String.self)
}

With the prior release you would need to define the same class twice naming it differently. Now you can simply name the table when you create it for example:

let customers = Person(name: "customers")
let employees = Person(name: "employees")

MySQL 8 support and linker improvements

The update also adds support for MySQL version 8 and removes the requirements for specifying additional flags at build, test and run time on newer Swift releases.

MySQL version 8 includes some changes in the databases header files that were incompatible with version 5. We have updated the SwiftMySQL plugin to abstract these differences meaning the code you write will work on MySQL versions prior to and post 8.

We have also added pkg-config support so you no longer have to specify additional flags on your swift commands so long as you are running a MySQL release later than 5.5.


Example usage

Below is a sample function that can be used in a Kitura route to retrieve data from a database:

func grades(_ callback: @escaping (String) -> Void) -> Void {
    connection.connect() { result in
        guard result.success else {
            guard let error = result.asError else {
                return callback("Error connecting: Unknown Error")
            }
            return callback("Error connecting: \(error)")
        }
        // Build and execute your query here.

        // First build query
        let query = Select(grades.course, grades.grade, from: grades)

        // Execute query
        connection.execute(query: query) { result in
            guard let resultSet = result.asResultSet else {
                guard let error = result.asError else {
                    return callback("Error executing query: Unknown Error")
                }
                return callback("Error executing query: \(error)")
            }
            var retString = ""
            resultSet.getColumnTitles() { titles, error in
                guard let titles = titles else {
                    guard let error = error else {
                        return callback("Error fetching column titles: Unknown Error")
                    }
                    return callback("Error fetching column titles: \(error)")
                }
                for title in titles {
                    //The column names of the result.
                    retString.append("\(title.padding(toLength: 35, withPad: " ", startingAt: 0))")
                }
                retString.append("\n")

                resultSet.forEach() { row, error in
                    guard let row = row else {
                        // A null row means we have run out of results unless we encountered an error
                        if let error = error {
                            return callback("Error fetching row: \(error)")
                        }
                        // No error so all rows are processed, make final callback passing result.
                        return callback(retString)
                    }
                    for value in row {
                        if let value = value {
                            let valueStr = String(describing: value)
                            let padStr = valueStr.padding(toLength: 35, withPad: " ", startingAt: 0)
                            retString.append(padStr)
                        }
                    }
                    retString.append("\n")
                }
            }
        }
    }
}

When called you will see results that look similar to this:

course                             grade                              
How to build your first computer   99                                 
How to work at a rock quarry       71

The full example can be found in the Swift-Kuery-PostgreSQL GitHub repository within the Readme.md file.


Future

Now that Swift-Kuery is entirely asynchronous we are ready for the future of Swift. The new APIs should transition seamlessly to async/await, and it should also be possible to take advantage of the new database drivers being discussed in the Swift Server Working Group.