From 2db108529fd9f5cc04595ad5a31e1488d15df4d6 Mon Sep 17 00:00:00 2001 From: micimize Date: Fri, 2 Apr 2021 21:18:55 -0500 Subject: [PATCH 1/6] break Selectable methods into smaller interfaces based on return type. This makes it so users can expose more refined/foolproof apis to their application: ```dart extension TaskMethods on Task { /// won't make mistakes like using getSingle when using SingleOrNullSelectable get event => db.calendarEvents.forTask(this); /// autocomplete ignores getSingle, etc MultiSelectable get metrics => db.metrics.forTask(this); } ``` --- .../query_builder/statements/query.dart | 104 ++++++++++++------ 1 file changed, 69 insertions(+), 35 deletions(-) diff --git a/moor/lib/src/runtime/query_builder/statements/query.dart b/moor/lib/src/runtime/query_builder/statements/query.dart index ce576ee0..cc54500b 100644 --- a/moor/lib/src/runtime/query_builder/statements/query.dart +++ b/moor/lib/src/runtime/query_builder/statements/query.dart @@ -68,19 +68,22 @@ 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. +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. +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 +102,69 @@ 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. +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. +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 +180,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()); } From 720929e38ec363529ef5352102d3ed3c0a405107 Mon Sep 17 00:00:00 2001 From: micimize Date: Fri, 2 Apr 2021 21:58:07 -0500 Subject: [PATCH 2/6] fix path_provider dead link --- docs/pages/docs/Other engines/vm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/docs/Other engines/vm.md b/docs/pages/docs/Other engines/vm.md index 7e9149f8..9c0e7e78 100644 --- a/docs/pages/docs/Other engines/vm.md +++ b/docs/pages/docs/Other engines/vm.md @@ -73,7 +73,7 @@ LazyDatabase(() async { ``` Note: If you haven't shipped a version with `moor_flutter` to your users yet, you can drop the dependency -on `sqflite`. Instead, you can use `path_provider` which [works on Desktop](https://github.com/google/flutter-desktop-embedding/tree/master/plugins/flutter_plugins). +on `sqflite`. Instead, you can use `path_provider` which [works on Desktop](https://pub.dev/packages/path_provider). Please be aware that `FlutterQueryExecutor.inDatabaseFolder` might yield a different folder than `path_provider` on Android. This can cause data loss if you've already shipped a version using `moor_flutter`. In that case, using `getDatabasePath` from sqflite is the suggested solution. From bb3816980352509e8fa21ca8bda1383d9164c463 Mon Sep 17 00:00:00 2001 From: micimize Date: Sun, 4 Apr 2021 11:01:01 -0500 Subject: [PATCH 3/6] Selectable refinement: add docs, explanations, and examples --- .../docs/Getting started/writing_queries.md | 20 +++++- .../query_builder/statements/query.dart | 62 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/docs/pages/docs/Getting started/writing_queries.md b/docs/pages/docs/Getting started/writing_queries.md index 6bd154fb..0bcc24c1 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,25 @@ 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 - 1); +} + +// Exposes `getSingle` and `watchSingle` +SingleSelectable todoById(int id) => + select(todos)..where((t) => t.id.equals(id)); + +// Exposes `getSingleOrNull` and `watchSingleOrNull` +SingleOrNullSelectable todoFromExternalLink(int id) => + 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 cc54500b..7197b182 100644 --- a/moor/lib/src/runtime/query_builder/statements/query.dart +++ b/moor/lib/src/runtime/query_builder/statements/query.dart @@ -69,6 +69,23 @@ abstract class Query extends Component { } /// [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 - 1); +/// } +/// 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(); @@ -80,6 +97,24 @@ abstract class MultiSelectable { /// [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 @@ -118,6 +153,25 @@ abstract class SingleSelectable { /// [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 todoFromExternalLink(int id) => +/// select(todos)..where((t) => t.id.equals(id)); +/// +/// final idFromEmailLink = 100; +/// todoFromExternalLink(idFromEmailLink).getSingleOrNull(); +/// todoFromExternalLink(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 @@ -143,6 +197,14 @@ abstract class SingleOrNullSelectable { /// 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, From 7f83898d00eced8603b85b1c18b3eaa267cfabc7 Mon Sep 17 00:00:00 2001 From: micimize Date: Sun, 4 Apr 2021 11:12:48 -0500 Subject: [PATCH 4/6] normalize example methods based on docs elsewhere --- docs/pages/docs/Getting started/writing_queries.md | 4 ++-- .../src/runtime/query_builder/statements/query.dart | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/pages/docs/Getting started/writing_queries.md b/docs/pages/docs/Getting started/writing_queries.md index 0bcc24c1..7abaa32c 100644 --- a/docs/pages/docs/Getting started/writing_queries.md +++ b/docs/pages/docs/Getting started/writing_queries.md @@ -107,11 +107,11 @@ MultiSelectable pageOfTodos(int page, {int pageSize = 10}) { } // Exposes `getSingle` and `watchSingle` -SingleSelectable todoById(int id) => +SingleSelectable entryById(int id) => select(todos)..where((t) => t.id.equals(id)); // Exposes `getSingleOrNull` and `watchSingleOrNull` -SingleOrNullSelectable todoFromExternalLink(int id) => +SingleOrNullSelectable entryFromExternalLink(int id) => select(todos)..where((t) => t.id.equals(id)); ``` These base classes don't have query-building or `map` methods, signaling to the consumer diff --git a/moor/lib/src/runtime/query_builder/statements/query.dart b/moor/lib/src/runtime/query_builder/statements/query.dart index 7197b182..5253c95d 100644 --- a/moor/lib/src/runtime/query_builder/statements/query.dart +++ b/moor/lib/src/runtime/query_builder/statements/query.dart @@ -104,9 +104,8 @@ abstract class MultiSelectable { /// {@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))); -/// } +/// SingleSelectable entryById(int id) => +/// select(todos)..where((t) => t.id.equals(id)); /// final idGuaranteedToExist = 10; /// entryById(idGuaranteedToExist).getSingle(); /// entryById(idGuaranteedToExist).watchSingle(); @@ -161,12 +160,12 @@ abstract class SingleSelectable { /// {@template moor_single_or_null_selectable_example} ///```dart /// // Retrieve a todo from an external link that may not be valid. -/// SingleOrNullSelectable todoFromExternalLink(int id) => +/// SingleOrNullSelectable entryFromExternalLink(int id) => /// select(todos)..where((t) => t.id.equals(id)); /// /// final idFromEmailLink = 100; -/// todoFromExternalLink(idFromEmailLink).getSingleOrNull(); -/// todoFromExternalLink(idFromEmailLink).watchSingleOrNull(); +/// entryFromExternalLink(idFromEmailLink).getSingleOrNull(); +/// entryFromExternalLink(idFromEmailLink).watchSingleOrNull(); /// ``` /// {@endtemplate} /// From fb2423c28fd33a98389359f0fffa797813fbc486 Mon Sep 17 00:00:00 2001 From: micimize Date: Sun, 4 Apr 2021 11:25:15 -0500 Subject: [PATCH 5/6] use arrow functions and entry* naming for singletons --- docs/pages/docs/Getting started/writing_queries.md | 5 ++--- moor/lib/src/runtime/query_builder/statements/query.dart | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/pages/docs/Getting started/writing_queries.md b/docs/pages/docs/Getting started/writing_queries.md index 7abaa32c..d9e389d6 100644 --- a/docs/pages/docs/Getting started/writing_queries.md +++ b/docs/pages/docs/Getting started/writing_queries.md @@ -102,9 +102,8 @@ 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 - 1); -} +MultiSelectable pageOfTodos(int page, {int pageSize = 10}) => + select(todos)..limit(pageSize, offset: page); // Exposes `getSingle` and `watchSingle` SingleSelectable entryById(int id) => diff --git a/moor/lib/src/runtime/query_builder/statements/query.dart b/moor/lib/src/runtime/query_builder/statements/query.dart index 5253c95d..8642b745 100644 --- a/moor/lib/src/runtime/query_builder/statements/query.dart +++ b/moor/lib/src/runtime/query_builder/statements/query.dart @@ -76,9 +76,8 @@ abstract class Query extends Component { /// {@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 - 1); -/// } +/// MultiSelectable pageOfTodos(int page, {int pageSize = 10}) => +/// select(todos)..limit(pageSize, offset: page); /// pageOfTodos(1).get(); /// pageOfTodos(1).watch(); /// ``` From 03f9c03ec7565ac46d8eab8055591eb344f96787 Mon Sep 17 00:00:00 2001 From: micimize Date: Sun, 4 Apr 2021 11:28:52 -0500 Subject: [PATCH 6/6] standardize docstrings --- .../docs/Getting started/writing_queries.md | 15 +++++++++------ .../runtime/query_builder/statements/query.dart | 16 +++++++++------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/pages/docs/Getting started/writing_queries.md b/docs/pages/docs/Getting started/writing_queries.md index d9e389d6..336b3f10 100644 --- a/docs/pages/docs/Getting started/writing_queries.md +++ b/docs/pages/docs/Getting started/writing_queries.md @@ -102,16 +102,19 @@ 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}) => - select(todos)..limit(pageSize, offset: page); +MultiSelectable pageOfTodos(int page, {int pageSize = 10}) { + return select(todos)..limit(pageSize, offset: page); +} // Exposes `getSingle` and `watchSingle` -SingleSelectable entryById(int id) => - select(todos)..where((t) => t.id.equals(id)); +SingleSelectable entryById(int id) { + return select(todos)..where((t) => t.id.equals(id)); +} // Exposes `getSingleOrNull` and `watchSingleOrNull` -SingleOrNullSelectable entryFromExternalLink(int id) => - select(todos)..where((t) => t.id.equals(id)); +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. diff --git a/moor/lib/src/runtime/query_builder/statements/query.dart b/moor/lib/src/runtime/query_builder/statements/query.dart index 8642b745..60c06330 100644 --- a/moor/lib/src/runtime/query_builder/statements/query.dart +++ b/moor/lib/src/runtime/query_builder/statements/query.dart @@ -76,8 +76,9 @@ abstract class Query extends Component { /// {@template moor_multi_selectable_example} /// ```dart /// /// Retrieve a page of [Todo]s. -/// MultiSelectable pageOfTodos(int page, {int pageSize = 10}) => -/// select(todos)..limit(pageSize, offset: page); +/// MultiSelectable pageOfTodos(int page, {int pageSize = 10}) { +/// return select(todos)..limit(pageSize, offset: page); +/// } /// pageOfTodos(1).get(); /// pageOfTodos(1).watch(); /// ``` @@ -103,8 +104,9 @@ abstract class MultiSelectable { /// {@template moor_single_selectable_example} /// ```dart /// // Retrieve a todo known to exist. -/// SingleSelectable entryById(int id) => -/// select(todos)..where((t) => t.id.equals(id)); +/// SingleSelectable entryById(int id) { +/// return select(todos)..where((t) => t.id.equals(id)); +/// } /// final idGuaranteedToExist = 10; /// entryById(idGuaranteedToExist).getSingle(); /// entryById(idGuaranteedToExist).watchSingle(); @@ -159,9 +161,9 @@ abstract class SingleSelectable { /// {@template moor_single_or_null_selectable_example} ///```dart /// // Retrieve a todo from an external link that may not be valid. -/// SingleOrNullSelectable entryFromExternalLink(int id) => -/// select(todos)..where((t) => t.id.equals(id)); -/// +/// SingleOrNullSelectable entryFromExternalLink(int id) { +/// return select(todos)..where((t) => t.id.equals(id)); +/// } /// final idFromEmailLink = 100; /// entryFromExternalLink(idFromEmailLink).getSingleOrNull(); /// entryFromExternalLink(idFromEmailLink).watchSingleOrNull();