New release: GRDB 4.2

Hello Swift Community,

GRDB, the "toolkit for SQLite databases, with a focus on application development" has been upgraded to version 4.2.0 (release notes).

Besides a few bug fixes, this release brings two enhancements:

  • A simpler way to observe the database

    ValueObservation is the GRDB type that can track database changes. With GRDB 4.2,you can now write:

    // New!
    let observation = ValueObservation.tracking { db in
        try Player.fetchAll(db)
    }
    

    In the closure argument of ValueObservation.tracking(value:), you just fetch the values you are interested in. As many values, from as many tables as you want. If you know how to fetch, you know how to observe.

    This new method solves several problems with GRDB 4.1, which made it sometimes difficult to express complex observations. I can't help but illustrate the changes with a slightly more involved example, so that you can see if you would benefit from the upgrade:

    Before / After
    // Let's observe both the number of players, and the ten best ones
    
    // GRDB 4.1: convoluted
    let playerCountObservation = Player.observationForCount()
    let bestPlayersObservation = Player.order(Column("score").desc).limit(10).observationForAll()
    let observation = ValueObservation.combine(
        playerCountObservation,
        bestPlayersObservation)
    
    // GRDB 4.2: straight to the point
    let observation = ValueObservation.tracking { db -> (Int, [Player]) in
        let count = Player.fetchCount(db)
        let players = Player.order(Column("score").desc).limit(10).fetchAll(db)
        return (count, players)
    }
    

    Please read the new ValueObservation documentation chapter for more information. And check GRDBCombine or RxGRDB if you feel like adding a reactive topping.

  • Enhanced requests and associations scoping

    GRDB 4.2 makes new methods available to constrained extensions to the DerivableRequest protocol: filter(key:), filter(keys:), orderByPrimaryKey(), and the full-text filter matching(_:).

    OK, but what is the DerivableRequest protocol already?

    GRDB likes it when you extend the built-in query language with your own methods:

    // Good
    extension QueryInterface where RowDecoder == Player {
        func best(_ count: Int) -> QueryInterface<Player> {
            return self.order(Column("score").desc).limit(count)
        }
        
        func filter(role: Role) -> QueryInterface<Player> {
            return self.filter(Column("role") == role)
        }
    }
    
    // πŸ‘ Clarity at the call site
    let bestPlayers = Player.all().best(10)
    let bestCaptains = Player.all().filter(role: .captain).best(5)
    

    Since Associations have been introduced, you can extend both requests and associations with constrained extensions to the DerivableRequest protocol:

    // Better
    extension DerivableRequest where RowDecoder == Player {
        func best(_ count: Int) -> Self {
            return self.order(Column("score").desc).limit(count)
        }
        
        func filter(role: Role) -> Self {
            return self.filter(Column("role") == role)
        }
    }
    
    // πŸ‘ DerivableRequest extension applies to requests as before:
    let bestPlayers = Player.all().best(10)
    let bestCaptains = Player.all().filter(role: .captain).best(5)
    
    // πŸ‘Œ It also applies to associations!
    
    // All teams with their three best players
    let teams = Team.including(all: Team.players.best(3))
    
    // All teams with their captain
    let teams = Team.including(required: Team.players.filter(role: .captain))
    

    GRBD 4.2 enlarges the list of methods available to DerivableRequest extensions. For example, you can now perform full-text search:

    extension DerivableRequest where RowDecoder == Document {
        func filter(searchString: String) -> Self {
            let pattern = FTS5Pattern(matchingAnyTokenIn: searchString)
            return self.matching(pattern)
        }
    }
    
    // Documents that match
    let documents = Documents.all().filter(searchString: textField.text)
    
    // Libraries that contain documents that match
    let libraries = Library.having(Library.documents
        .filter(searchString: textField.text)
        .isEmpty == false)
    

Want to know more? Check the full release notes.

Happy GRDB!

3 Likes