Add option to disable database change filtering in TransactionObserver - #1864
Conversation
|
Thank you @simolus3! I'll review this pull request in the next few days. At first glance, it does exactly what we talked about, and it does it well 👍 Please tell me if it allows PowerSync to achieve the goals you described in #1863, and in particular if later on you can call |
Yes, I've tested these changes with PowerSync and they seem to do exactly what we need :) My approach right now is to add a catchall observer in a |
|
I had a closer look, and I could spot a design flaw 🙂 Your changes closely reflect our initial intent, but I gave you a misleading direction. It won't be difficult to fix, fortunately! The symptom of the design flaw is that the new test fails if the observer returns false from private class EverythingObserver: TransactionObserver {
func observes(statement: Statement) -> Bool {
return true
}
func observes(eventsOfKind eventKind: GRDB.DatabaseEventKind) -> Bool {
- return true // test passes
+ return false // test fails
}
}This is definitely a bug. With this modification of the test, the The cause is that SQLite compiles the And there lies the design flaw: the truncate optimization is enabled or disabled from the compile-time authorization callbacks, before we can call The intention described at #1863 (comment) still holds:
It turns wrong in the next paragraph:
I think our only solution is to let observers opt in for all update events, without any filtering: protocol TransactionObserver {
- func observes(statement: Statement) -> Bool
+ var observesAllDatabaseChanges: Bool { get }
}If you lack time, I'll update this pull request accordingly. The |
That will definitely work for PowerSync too, I've changed the PR to be based on that 👍
I've added a small note on
I suppose "Changes performed by SQLite statements that are not both compiled and executed through GRDB APIs" implicitly covers that (since the function executes the statement with direct SQLite APIs internally, not with GRDB APIs). But it's good to be explicit about this, and I've expanded that bullet point.
I'm not really sure where we'd mention that, in the section about transaction observers controlling for which events they'd be invoked? I would be happy to expand the documentation with a bit more help from your side, but if you think it's quicker if you update this PR, then that's definitely fine with me as well :) |
|
Sounds great! OK I take charge of the remaining tidbits 👍 Thank you very much for your help 🙏 |
The issue with `observesAllDatabaseChanges` is that this was a misleading name: transaction observers are not notified of changes that are performed in a save point that is eventually rollbacked. By exposing the DatabaseEventObservationStrategy type, we allow for a future extra flag that ignores save points, trading observation precision for a reduced memory footprint.
|
Hello @simolus3, sorry for the delay. May is a lazy month in France, and a heat wave did not help 😅 I performed a last refactor, due to the misleading By exposing a The universal observer you need is now written as below: // An observer that observes all database changes.
class UniversalObserver: TransactionObserver {
var databaseEventObservationStrategy: DatabaseEventObservationStrategy {
var strategy = DatabaseEventObservationStrategy.default
// Don't filter on database event kind, so that we are
// notified of changes performed through the SQLite C API:
strategy.requiresDatabaseEventKind = false
return strategy
}
// You still have to provide an implementation for this method,
// but it will never be called since `requiresDatabaseEventKind`
// is false.
func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool {
false // ignored
}
func databaseDidChange(with event: DatabaseEvent) {
// Handle the change
}
} |
|
Thanks for all your help with this :) I agree that |
|
Thank you for your patience. Full test suite is still running, I'll ship shortly after, probably tomorrow. |
observe(statement:) to TransactionObserver|
⛵ The feature was shipped in v7.11.0 :-) |
Add `observe(statement:)` to `TransactionObserver`
Some statements might issue writes to the databases through a layer of indirection (e.g. via a user-defined function). For these statements, GRDB is unable to detect the writes made and doesn't call
TransactionObserver.observes(eventKind:), doesn't install an update listener and has no way of observing these writes.This adds
TransactionObserver.observesAllDatabaseChanges, which can be used to make transaction observers listen for all database changes. It returnsfalseby default, but can be added to implementations to observe indirect statements as well.Closes #1863.
Pull Request Checklist
developmentbranch.make smokeTestterminal command runs without failure.