I've been scouring the documentation and source code to try establish the inner workings of watch() and watchSingle(), without any success.
Is there a chance of providing some documentation on this on https://moor.simonbinder.eu/docs/ ?
Specifically, I'd like to know what SQL statements are being used (I'm guessing triggers?), what the considerations are for re-use/disposal, and if there is advice on best practice in using these (such as efficiency and if we need to consider regular gets in some instances). I would imagine that using a watch on a large group of tables combined could be relatively expensive/dangerous.
Most moor internals aren't explained on the website because users rarely need to know them. I'd say the source code is fairly well commented though, so if you have issues with that I'm glad to improve readability. The core of stream queries is implemented here. I'm happy to answer your questions though:
'd like to know what SQL statements are being used (I'm guessing triggers?)
We don't use any special SQL features to implement stream queries, that feature is implemented in Dart. Basically,
FOREIGN KEY ... ON DELETE CASCADE, we know that a delete on some table can cause a delete on other tables too.With that information, it's relatively straightforward to implement stream queries. A .watch() basically listens on a set of tables. When a write on any of these tables happens, we run the query again.
what the considerations are for re-use/disposal
If you .watch() the same SQL query (with the same variables) more than once, that has virtually no additional cost. We cache queries and their results, so we won't run the query more often in that case. We dispose a query when there are no remaining listens (a tiny bit later actually, since in Flutter its common for say a StreamBuilder to disconnect and then immediately listen again when setState() is called, we wouldn't want to clear the cache in that case).
such as efficiency and if we need to consider regular gets in some instances
What you should now is that stream updates aren't _exact_, they update more often than they need to. So if you had SELECT * FROM users WHERE id = 3 for instance, the query would invalidate whenever anything is written to the users table. However, I'm not aware of any performance issues caused by stream queries even in large apps with lots of concurrently active streams. Most select statements are really cheap.
If you do have very complex select statements, it can make sense to not .watch() them or implement your own invalidation logic to avoid unnecessary updates. But again, I've never heard of a use-case where that would be necessary.
Thank you for the explanation, that is helpful! I'll add to this issue if I think of anything else in the near future.