Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ColumnExpression Protocol #340

Merged
merged 6 commits into from
Apr 23, 2018
Merged

ColumnExpression Protocol #340

merged 6 commits into from
Apr 23, 2018

Conversation

groue
Copy link
Owner

@groue groue commented Apr 22, 2018

There used to be only one column type: Column.

  1. Column had an undocumented (little) privilege: it could enhance observation of fetch requests that target specific row ids, such as Player.filter(Column("id") == 1).

    Observation tools like RxGRDB notify of changes in the results of such request if and only if the player with id 1 is modified or deleted.

    Conversely, raw sql requests like SELECT * FROM players WHERE id = 1, or requests built from custom column types, like Player.filter(CustomColumn("id") == 1) (ping @freak4pc) would not profit for this precise observation: any change in any row would trigger a change notification.

    This PR does not bring precise observation to raw SQL requests. But it does for custom column types (if they adopt the new ColumnExpression protocol).

  2. The recommended way to declare columns attached to a record type was a namespace enum and static declarations:

    struct Player: FetchableRecord, ... {
        var id: Int64?
        var name: String
        var score: Int
        
        // Typical GRDB 2
        enum Columns {
            static let id = Column("id")
            static let name = Column("name")
            static let score = Column("score")
        }
    }

    This works, but it is not very sexy.

The new ColumnExpression protocol fixes both problems:

/// The protocol for types that define database columns
public protocol ColumnExpression: SQLExpression {
    /// The unqualified name of a database column.
    /// "score" is a valid unqualified name. "player.score" is not.
    var name: String { get }
}

Customized column types now just have to adopt ColumnExpression in order to trigger precise request observation.

And it is easier to define record columns:

struct Player: FetchableRecord, ... {
    var id: Int64?
    var name: String
    var score: Int
    
    enum Columns: String, ColumnExpression {
        case id, name, score
    }
    
    static func best(_ count: Int) -> QueryInterfaceRequest<Player> {
        return all()
            .order(Columns.score.desc)
            .limit(count)
    }
}

let topTenPlayers = try Player.best(10).fetchAll(db)

Codable records can also turn their coding keys into columns, if they want:

struct Player: Codable, FetchableRecord, ... {
    var id: Int64?
    var name: String
    var score: Int
    
    private enum CodingKeys: CodingKey, ColumnExpression {
        case id, name, score
        var name: String { return stringValue }
    }
    
    static func best(_ count: Int) -> QueryInterfaceRequest<Player> {
        return all()
            .order(CodingKeys.score.desc)
            .limit(count)
    }
}

let topTenPlayers = try Player.best(10).fetchAll(db)

@groue groue added this to the GRDB 3.0 milestone Apr 22, 2018
@groue groue mentioned this pull request Apr 22, 2018
29 tasks
@groue groue merged commit 32ec750 into GRDB3 Apr 23, 2018
@groue groue deleted the GRDB3-SQLColumnExpression branch April 23, 2018 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant