diff --git a/docs/pages/docs/Getting started/writing_queries.md b/docs/pages/docs/Getting started/writing_queries.md index 6bd154fb..336b3f10 100644 --- a/docs/pages/docs/Getting started/writing_queries.md +++ b/docs/pages/docs/Getting started/writing_queries.md @@ -84,7 +84,6 @@ more than one entry (which is impossible in this case), an error will be added instead. ### Mapping - Before calling `watch` or `get` (or the single variants), you can use `map` to transform the result. ```dart @@ -98,6 +97,27 @@ Stream> contentWithLongTitles() { } ``` +### Deferring get vs watch +If you want to make your query consumable as either a `Future` or a `Stream`, +you can refine your return type using one of the `Selectable` abstract base classes; +```dart +// Exposes `get` and `watch` +MultiSelectable pageOfTodos(int page, {int pageSize = 10}) { + return select(todos)..limit(pageSize, offset: page); +} + +// Exposes `getSingle` and `watchSingle` +SingleSelectable entryById(int id) { + return select(todos)..where((t) => t.id.equals(id)); +} + +// Exposes `getSingleOrNull` and `watchSingleOrNull` +SingleOrNullSelectable entryFromExternalLink(int id) { + return select(todos)..where((t) => t.id.equals(id)); +} +``` +These base classes don't have query-building or `map` methods, signaling to the consumer +that they are complete results. If you need more complex queries with joins or custom columns, see [this site]({{ "../Advanced Features/joins.md" | pageUrl }}). diff --git a/moor/lib/src/runtime/query_builder/statements/query.dart b/moor/lib/src/runtime/query_builder/statements/query.dart index ce576ee0..60c06330 100644 --- a/moor/lib/src/runtime/query_builder/statements/query.dart +++ b/moor/lib/src/runtime/query_builder/statements/query.dart @@ -68,19 +68,57 @@ abstract class Query extends Component { } } -/// Abstract class for queries which can return one-time values or a stream -/// of values. -abstract class Selectable { +/// [Selectable] methods for returning multiple results. +/// +/// Useful for refining the return type of a query, while still delegating +/// whether to [get] or [watch] results to the consuming code. +/// +/// {@template moor_multi_selectable_example} +/// ```dart +/// /// Retrieve a page of [Todo]s. +/// MultiSelectable pageOfTodos(int page, {int pageSize = 10}) { +/// return select(todos)..limit(pageSize, offset: page); +/// } +/// pageOfTodos(1).get(); +/// pageOfTodos(1).watch(); +/// ``` +/// {@endtemplate} +/// +/// See also: [SingleSelectable] and [SingleOrNullSelectable] for exposing +/// single value methods. +abstract class MultiSelectable { /// Executes this statement and returns the result. Future> get(); /// Creates an auto-updating stream of the result that emits new items /// whenever any table used in this statement changes. Stream> watch(); +} - /// Executes this statement, like [get], but only returns one value. If the - /// query returns no or too many rows, the returned future will complete with - /// an error. +/// [Selectable] methods for returning or streaming single, +/// non-nullable results. +/// +/// Useful for refining the return type of a query, while still delegating +/// whether to [getSingle] or [watchSingle] results to the consuming code. +/// +/// {@template moor_single_selectable_example} +/// ```dart +/// // Retrieve a todo known to exist. +/// SingleSelectable entryById(int id) { +/// return select(todos)..where((t) => t.id.equals(id)); +/// } +/// final idGuaranteedToExist = 10; +/// entryById(idGuaranteedToExist).getSingle(); +/// entryById(idGuaranteedToExist).watchSingle(); +/// ``` +/// {@endtemplate} +/// +/// See also: [MultiSelectable] for exposing multi-value methods and +/// [SingleOrNullSelectable] for exposing nullable value methods. +abstract class SingleSelectable { + /// Executes this statement, like [Selectable.get], but only returns one + /// value. the query returns no or too many rows, the returned future will + /// complete with an error. /// /// {@template moor_single_query_expl} /// Be aware that this operation won't put a limit clause on this statement, @@ -99,20 +137,96 @@ abstract class Selectable { /// clause will only allow one row. /// {@endtemplate} /// - /// See also: [getSingleOrNull], which returns `null` instead of throwing if - /// the query completes with no rows. + /// See also: [Selectable.getSingleOrNull], which returns `null` instead of + /// throwing if the query completes with no rows. + Future getSingle(); + + /// Creates an auto-updating stream of this statement, similar to + /// [Selectable.watch]. However, it is assumed that the query will only emit + /// one result, so instead of returning a `Stream>`, this returns a + /// `Stream`. If, at any point, the query emits no or more than one rows, + /// an error will be added to the stream instead. + /// + /// {@macro moor_single_query_expl} + Stream watchSingle(); +} + +/// [Selectable] methods for returning or streaming single, +/// nullable results. +/// +/// Useful for refining the return type of a query, while still delegating +/// whether to [getSingleOrNull] or [watchSingleOrNull] result to the +/// consuming code. +/// +/// {@template moor_single_or_null_selectable_example} +///```dart +/// // Retrieve a todo from an external link that may not be valid. +/// SingleOrNullSelectable entryFromExternalLink(int id) { +/// return select(todos)..where((t) => t.id.equals(id)); +/// } +/// final idFromEmailLink = 100; +/// entryFromExternalLink(idFromEmailLink).getSingleOrNull(); +/// entryFromExternalLink(idFromEmailLink).watchSingleOrNull(); +/// ``` +/// {@endtemplate} +/// +/// See also: [MultiSelectable] for exposing multi-value methods and +/// [SingleSelectable] for exposing non-nullable value methods. +abstract class SingleOrNullSelectable { + /// Executes this statement, like [Selectable.get], but only returns one + /// value. If the result too many values, this method will throw. If no + /// row is returned, `null` will be returned instead. + /// + /// {@macro moor_single_query_expl} + /// + /// See also: [Selectable.getSingle], which can be used if the query will + /// always evaluate to exactly one row. + Future getSingleOrNull(); + + /// Creates an auto-updating stream of this statement, similar to + /// [Selectable.watch]. However, it is assumed that the query will only + /// emit one result, so instead of returning a `Stream>`, this + /// returns a `Stream`. If the query emits more than one row at + /// some point, an error will be emitted to the stream instead. + /// If the query emits zero rows at some point, `null` will be added + /// to the stream instead. + /// + /// {@macro moor_single_query_expl} + Stream watchSingleOrNull(); +} + +/// Abstract class for queries which can return one-time values or a stream +/// of values. +/// +/// If you want to make your query consumable as either a [Future] or a +/// [Stream], you can refine your return type using one of Selectable's +/// base classes: +/// +/// {@macro moor_multi_selectable_example} +/// {@macro moor_single_selectable_example} +/// {@macro moor_single_or_null_selectable_example} +abstract class Selectable + implements + MultiSelectable, + SingleSelectable, + SingleOrNullSelectable { + @override + Future> get(); + + @override + Stream> watch(); + + @override Future getSingle() async { return (await get()).single; } - /// Executes this statement, like [get], but only returns one value. If the - /// result too many values, this method will throw. If no row is returned, - /// `null` will be returned instead. - /// - /// {@macro moor_single_query_expl} - /// - /// See also: [getSingle], which can be used if the query will never evaluate - /// to exactly one row. + @override + Stream watchSingle() { + return watch().transform(singleElements()); + } + + @override Future getSingleOrNull() async { final list = await get(); final iterator = list.iterator; @@ -128,25 +242,7 @@ abstract class Selectable { return element; } - /// Creates an auto-updating stream of this statement, similar to [watch]. - /// However, it is assumed that the query will only emit one result, so - /// instead of returning a `Stream>`, this returns a `Stream`. If, - /// at any point, the query emits no or more than one rows, an error will be - /// added to the stream instead. - /// - /// {@macro moor_single_query_expl} - Stream watchSingle() { - return watch().transform(singleElements()); - } - - /// Creates an auto-updating stream of this statement, similar to [watch]. - /// However, it is assumed that the query will only emit one result, so - /// instead of returning a `Stream>`, this returns a `Stream`. If - /// the query emits more than one row at some point, an error will be emitted - /// to the stream instead. If the query emits zero rows at some point, `null` - /// will be added to the stream instead. - /// - /// {@macro moor_single_query_expl} + @override Stream watchSingleOrNull() { return watch().transform(singleElementsOrNull()); }