DatabaseRegionObservation

DatabaseRegionObservation tracks changes in a database region, and notifies impactful transactions.

DatabaseRegionObservation.swift:6DatabaseRegionObservation.md
struct DatabaseRegionObservation

Overview

DatabaseRegionObservation tracks insertions, updates, and deletions that impact the tracked region, whether performed with raw SQL, or Records and the Query Interface. This includes indirect changes triggered by foreign keys actions or SQL triggers.

See Dealing with Undetected Changes below for the list of exceptions.

DatabaseRegionObservation calls your application right after changes have been committed in the database, and before any other thread had any opportunity to perform further changes. This is a pretty strong guarantee, that most applications do not really need. Instead, most applications prefer to be notified with fresh values: make sure you check ValueObservation before using DatabaseRegionObservation.

DatabaseRegionObservation Usage

Create a DatabaseRegionObservation with one or several requests to track:

// Tracks the full player table
let observation = DatabaseRegionObservation(tracking: Player.all())

Then start the observation from a DatabaseQueue or DatabasePool:

let cancellable = try observation.start(in: dbQueue) { error in
    // Handle error
} onChange: { (db: Database) in
    print("Players were changed")
}

Enjoy the changes notifications:

try dbQueue.write { db in
    try Player(name: "Arthur").insert(db)
}
// Prints "Players were changed"

You stop the observation by calling the cancel method on the object returned by the start method. Cancellation is automatic when the cancellable is deallocated:

cancellable.cancel()

DatabaseRegionObservation can also be turned into a Combine publisher, or an RxSwift observable (see the companion library RxGRDB):

let cancellable = observation.publisher(in: dbQueue).sink { completion in
    // Handle completion
} receiveValue: { (db: Database) in
    print("Players were changed")
}

You can feed DatabaseRegionObservation with any type that conforms to the DatabaseRegionConvertible protocol: FetchRequest, DatabaseRegion, Table, etc. For example:

// Observe the score column of the 'player' table
let observation = DatabaseRegionObservation(
    tracking: Player.select(Column("score")))

// Observe the 'score' column of the 'player' table
let observation = DatabaseRegionObservation(
    tracking: SQLRequest("SELECT score FROM player"))

// Observe both the 'player' and 'team' tables
let observation = DatabaseRegionObservation(
    tracking: Table("player"), Table("team"))

// Observe the full database
let observation = DatabaseRegionObservation(
    tracking: .fullDatabase)

Dealing with Undetected Changes

DatabaseRegionObservation will not notify impactful transactions whenever the database is modified in an undetectable way:

  • Changes performed by external database connections.

  • Changes performed by SQLite statements that are not compiled and executed by GRDB.

  • Changes to the database schema, changes to internal system tables such as sqlite_master.

  • Changes to WITHOUT ROWID tables.

To have observations notify such undetected changes, applications can take explicit action: call the notifyChanges(in:) Database method from a write transaction:

try dbQueue.write { db in
    // Notify observations that some changes were performed in the database
    try db.notifyChanges(in: .fullDatabase)

    // Notify observations that some changes were performed in the player table
    try db.notifyChanges(in: Player.all())

    // Equivalent alternative
    try db.notifyChanges(in: Table("player"))
}

Creating DatabaseRegionObservation

Observing Database Transactions