DatabaseRegionObservation
DatabaseRegionObservation
tracks changes in a database region, and notifies impactful transactions.
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
init(tracking: any DatabaseRegionConvertible...
) Creates a
DatabaseRegionObservation
that notifies all transactions that modify one of the provided regions.init(tracking: [any DatabaseRegionConvertible]
) Creates a
DatabaseRegionObservation
that notifies all transactions that modify one of the provided regions.
Observing Database Transactions
publisher(in:)
func start(in: any DatabaseWriter, onError: @escaping (Error) -> Void, onChange: @escaping (Database) -> Void
) -> AnyDatabaseCancellable Starts observing the database.