From 46953c3a73523f5a97ea35c1285fb45426dc3f26 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Fri, 29 Mar 2024 10:44:59 -0400 Subject: [PATCH] Refactor ordering - Add better Docs --- drift/lib/src/runtime/manager/composer.dart | 28 ++++-- drift/lib/src/runtime/manager/filter.dart | 99 +++++++++------------ drift/lib/src/runtime/manager/join.dart | 5 -- drift/lib/src/runtime/manager/manager.dart | 28 +++--- drift/lib/src/runtime/manager/ordering.dart | 49 ++++++++-- drift_dev/lib/src/writer/manager.dart | 4 +- 6 files changed, 126 insertions(+), 87 deletions(-) diff --git a/drift/lib/src/runtime/manager/composer.dart b/drift/lib/src/runtime/manager/composer.dart index 201af3d8..55a8f7b3 100644 --- a/drift/lib/src/runtime/manager/composer.dart +++ b/drift/lib/src/runtime/manager/composer.dart @@ -21,7 +21,7 @@ class ComposerState /// Get a random alias for a table String _getRandomAlias(TableInfo table) { var aliasName = '${table.actualTableName}__${Random().nextInt(4294967296)}'; - while (joinBuilders.aliasedNames.contains(aliasName)) { + while (joinBuilders.map((e) => (e.aliasedName)).contains(aliasName)) { aliasName = '${table.actualTableName}__${Random().nextInt(4294967296)}'; continue; } @@ -44,10 +44,26 @@ class AliasedComposerBuilder f.id.equals(1) & f.name.equals('Bob')); +/// ``` +/// `f` in this example is a [Composer] object, and `f.id.equals(1) & f.name.equals('Bob')` is a [ComposableFilter] object. +/// +/// The [Composer] class is responsible for creating joins between tables, and passing them down to the composable classes. +/// This is done to ensure that duplicate joins are never created. +/// +/// The [ComposerState] that is held in this class only holds temporary state, as the final state will be held in the composable classes. +/// E.G. In the example above, the resulting [ComposableFilter] object is returned, and the [FilterComposer] is discarded. +/// @internal sealed class Composer { - /// The state of the query composer + /// The state of the composer final ComposerState state; Composer.withAliasedTable(AliasedComposerBuilder data) @@ -55,9 +71,7 @@ sealed class Composer { data.state.db, data.aliasedTable, data.state.joinBuilders); Composer.empty(DB db, CT table) : state = ComposerState._(db, table, {}); - /// Helper method for creaing an aliased join - /// and adding it to the state and Composable object - + /// Method for create a join between two tables B referenced, B extends HasJoinBuilders>({ required GeneratedColumn Function(CT) getCurrentColumn, @@ -107,8 +121,6 @@ sealed class Composer { // that state doesnt have, it is also possible that the result is missing // the `joinBuilder` we create above. // We will combine both sets and set it to `state.joinBuilders` and `result.joinBuilders` - - // Add the joins that may have been created in the filterBuilder to state for (var joinBuilder in result.joinBuilders.union(state.joinBuilders)) { state.addJoinBuilder(joinBuilder); result.addJoinBuilder(joinBuilder); diff --git a/drift/lib/src/runtime/manager/filter.dart b/drift/lib/src/runtime/manager/filter.dart index 2b5015cd..91b2f89c 100644 --- a/drift/lib/src/runtime/manager/filter.dart +++ b/drift/lib/src/runtime/manager/filter.dart @@ -1,6 +1,6 @@ part of 'manager.dart'; -/// Defines a class that can be used to compose filters for a column +/// Defines a class which is used to wrap a column to only expose filter functions class ColumnFilters { /// This class is a wrapper on top of the generated column class /// @@ -18,111 +18,99 @@ class ColumnFilters { /// Column that this [ColumnFilters] wraps GeneratedColumn column; - // ignore: public_member_api_docs + /// Create a filter that checks if the column is null. ComposableFilter isNull() => ComposableFilter.simple(column.isNull()); - // ignore: public_member_api_docs + + /// Create a filter that checks if the column is not null. ComposableFilter isNotNull() => ComposableFilter.simple(column.isNotNull()); - // ignore: public_member_api_docs + + /// Create a filter that checks if the column equals a value. ComposableFilter equals(T value) => ComposableFilter.simple(column.equals(value)); + + /// Create a filter that checks if the column equals a value. + ComposableFilter call(T value) => + ComposableFilter.simple(column.equals(value)); } /// Built in filters for int/double columns extension NumFilters on ColumnFilters { - // ignore: public_member_api_docs + /// Create a filter to check if the column is bigger than a value ComposableFilter isBiggerThan(T value) => ComposableFilter.simple(column.isBiggerThanValue(value)); -// ignore: public_member_api_docs - ComposableFilter isNotBiggerThan(T value) => isBiggerThan(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is small than a value ComposableFilter isSmallerThan(T value) => ComposableFilter.simple(column.isSmallerThanValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotSmallerThan(T value) => - isSmallerThan(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is bigger or equal to a value ComposableFilter isBiggerOrEqualTo(T value) => ComposableFilter.simple(column.isBiggerOrEqualValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotBiggerOrEqualTo(T value) => - isBiggerOrEqualTo(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is small or equal to a value ComposableFilter isSmallerOrEqualTo(T value) => ComposableFilter.simple(column.isSmallerOrEqualValue(value)); -// ignore: public_member_api_docs - ComposableFilter isNotSmallerOrEqualTo(T value) => - isSmallerOrEqualTo(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is between two values ComposableFilter isBetween(T lower, T higher) => ComposableFilter.simple(column.isBetweenValues(lower, higher)); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is not between two values ComposableFilter isNotBetween(T lower, T higher) => isBetween(lower, higher)._reversed(); } /// Built in filters for BigInt columns extension BigIntFilters on ColumnFilters { - // ignore: public_member_api_docs + /// Create a filter to check if the column is bigger than a value ComposableFilter isBiggerThan(T value) => ComposableFilter.simple(column.isBiggerThanValue(value)); -// ignore: public_member_api_docs - ComposableFilter isNotBiggerThan(T value) => isBiggerThan(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is small than a value ComposableFilter isSmallerThan(T value) => ComposableFilter.simple(column.isSmallerThanValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotSmallerThan(T value) => - isSmallerThan(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is bigger or equal to a value ComposableFilter isBiggerOrEqualTo(T value) => ComposableFilter.simple(column.isBiggerOrEqualValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotBiggerOrEqualTo(T value) => - isBiggerOrEqualTo(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is small or equal to a value ComposableFilter isSmallerOrEqualTo(T value) => ComposableFilter.simple(column.isSmallerOrEqualValue(value)); -// ignore: public_member_api_docs - ComposableFilter isNotSmallerOrEqualTo(T value) => - isSmallerOrEqualTo(value)._reversed(); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is between two values ComposableFilter isBetween(T lower, T higher) => ComposableFilter.simple(column.isBetweenValues(lower, higher)); -// ignore: public_member_api_docs + + /// Create a filter to check if the column is not between two values ComposableFilter isNotBetween(T lower, T higher) => isBetween(lower, higher)._reversed(); } /// Built in filters for String columns extension DateFilters on ColumnFilters { -// ignore: public_member_api_docs + /// Create a filter to check if the column is after a [DateTime] ComposableFilter isAfter(T value) => ComposableFilter.simple(column.isBiggerThanValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotAfter(T value) => isAfter(value)._reversed(); - // ignore: public_member_api_docs + /// Create a filter to check if the column is before a [DateTime] ComposableFilter isBefore(T value) => ComposableFilter.simple(column.isSmallerThanValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotBefore(T value) => isBefore(value)._reversed(); - // ignore: public_member_api_docs + /// Create a filter to check if the column is on or after a [DateTime] ComposableFilter isAfterOrOn(T value) => ComposableFilter.simple(column.isBiggerOrEqualValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotAfterOrOn(T value) => isAfterOrOn(value)._reversed(); - // ignore: public_member_api_docs + /// Create a filter to check if the column is before or on a [DateTime] ComposableFilter isBeforeOrOn(T value) => ComposableFilter.simple(column.isSmallerOrEqualValue(value)); - // ignore: public_member_api_docs - ComposableFilter isNotBeforeOrOn(T value) => isBeforeOrOn(value)._reversed(); - // ignore: public_member_api_docs + /// Create a filter to check if the column is between 2 [DateTime]s + ComposableFilter isBetween(T lower, T higher) => ComposableFilter.simple(column.isBetweenValues(lower, higher)); - // ignore: public_member_api_docs + + /// Create a filter to check if the column is not between 2 [DateTime]s ComposableFilter isNotBetween(T lower, T higher) => isBetween(lower, higher)._reversed(); } @@ -186,12 +174,11 @@ class ComposableFilter implements HasJoinBuilders { } /// The class that orchestrates the composition of filtering -class FilterComposer +class FilterComposer extends Composer { - /// Create a new filter composer from existing query state - // FilterComposer(super.state); - /// Create a filter composer with an empty state FilterComposer.empty(super.db, super.table) : super.empty(); + + /// Create a filter composer using another composers state FilterComposer.withAliasedTable(super.data) : super.withAliasedTable(); } diff --git a/drift/lib/src/runtime/manager/join.dart b/drift/lib/src/runtime/manager/join.dart index 7868ba86..968030be 100644 --- a/drift/lib/src/runtime/manager/join.dart +++ b/drift/lib/src/runtime/manager/join.dart @@ -58,8 +58,3 @@ abstract interface class HasJoinBuilders { /// Add a join builder to this class void addJoinBuilder(JoinBuilder builder); } - -/// Helper for getting all the aliased names of a set of join builders -extension on Set { - List get aliasedNames => map((e) => (e.aliasedName)).toList(); -} diff --git a/drift/lib/src/runtime/manager/manager.dart b/drift/lib/src/runtime/manager/manager.dart index a6c53a81..9d14ae67 100644 --- a/drift/lib/src/runtime/manager/manager.dart +++ b/drift/lib/src/runtime/manager/manager.dart @@ -7,10 +7,10 @@ part 'filter.dart'; part 'join.dart'; part 'ordering.dart'; -/// Defines a class that holds the state for a table manager +/// Defines a class that holds the state for a [BaseTableManager] class TableManagerState< DB extends GeneratedDatabase, - T extends TableInfo, + T extends Table, DT extends DataClass, FS extends FilterComposer, OS extends OrderingComposer> { @@ -23,10 +23,12 @@ class TableManagerState< /// The expression that will be applied to the query final Expression? filter; - /// The orderings that will be applied to the query + /// A set of [OrderingBuilder] which will be used to apply + /// [OrderingTerm]s to the statement when it's eventually built final Set orderingBuilders; - /// The joins that will be applied to the query + /// A set of [JoinBuilder] which will be used to create [Join]s + /// that will be applied to the build statement final Set joinBuilders; /// Whether the query should return distinct results @@ -38,15 +40,19 @@ class TableManagerState< /// If set, the number of rows that will be skipped final int? offset; - /// The composer for the filters + /// The [FilterComposer] for this [TableManagerState] + /// This class will be used to create filtering [Expression]s + /// which will be applied to the statement when its eventually created final FS filteringComposer; - /// The composer for the orderings + /// The [OrderingComposer] for this [TableManagerState] + /// This class will be used to create [OrderingTerm]s + /// which will be applied to the statement when its eventually created final OS orderingComposer; /// Defines a class which holds the state for a table manager /// It contains the database instance, the table instance, and any filters/orderings that will be applied to the query - /// This is held in a seperate class so that the state can be passed down from the root manager to the lower level managers + /// This is held in a seperate class than the [BaseTableManager] so that the state can be passed down from the root manager to the lower level managers TableManagerState({ required this.db, required this.table, @@ -88,7 +94,8 @@ class TableManagerState< /// This is needed due to dart's limitations with generics TableInfo get _tableAsTableInfo => table as TableInfo; - /// Builds a joined select statement. + /// Builds a joined select statement, should be used when joins are present + /// Will order, filter, and limit the statement using the state JoinedSelectStatement _buildJoinedSelectStatement() { // Build the joins final joins = joinBuilders.map((e) => e.buildJoin()).toList(); @@ -112,9 +119,10 @@ class TableManagerState< return statement; } - /// Builds a simple select statement + /// Builds a simple select statement, this should be used when there are no joins + /// Will order, filter, and limit the statement using the state SimpleSelectStatement _buildSimpleSelectStatement() { - // Create the joined statement + // Create the statement final statement = db.select(_tableAsTableInfo, distinct: distinct ?? false); // Apply the expression to the statement diff --git a/drift/lib/src/runtime/manager/ordering.dart b/drift/lib/src/runtime/manager/ordering.dart index ad14e4f2..84f52ccd 100644 --- a/drift/lib/src/runtime/manager/ordering.dart +++ b/drift/lib/src/runtime/manager/ordering.dart @@ -1,5 +1,35 @@ part of 'manager.dart'; +/// Defines a class which is used to wrap a column to only expose ordering functions +class ColumnOrderings { + /// This class is a wrapper on top of the generated column class + /// + /// It's used to expose ordering functions for a column + /// + /// ```dart + /// extension on FilterComposer{ + /// FitlerBuilder after2000() => isAfter(DateTime(2000)); + ///} + /// ``` + ColumnOrderings(this.column); + + /// Column that this [ColumnOrderings] wraps + GeneratedColumn column; + + /// Sort this column in ascending order + /// + /// 10 -> 1 | Z -> A | Dec 31 -> Jan 1 + ComposableOrdering asc() => + ComposableOrdering.simple({OrderingBuilder(OrderingMode.asc, column)}); + + /// Sort this column in descending order + /// + /// 1 -> 10 | A -> Z | Jan 1 -> Dec 31 + ComposableOrdering desc() => + ComposableOrdering.simple({OrderingBuilder(OrderingMode.desc, column)}); +} + +/// Defines a class which will hold the information needed to create an ordering class OrderingBuilder { /// The mode of the ordering final OrderingMode mode; @@ -7,6 +37,7 @@ class OrderingBuilder { /// The column that the ordering is applied to final GeneratedColumn column; + /// Create a new ordering builder, will be used by the [TableManagerState] to create [OrderingTerm]s OrderingBuilder(this.mode, this.column); @override @@ -26,7 +57,11 @@ class OrderingBuilder { } /// Defines a class that can be used to compose orderings for a column +/// +/// Multiple orderings can be composed together using the `&` operator. +/// The orderings will be executed from left to right. class ComposableOrdering implements HasJoinBuilders { + /// The orderings that are being composed final Set orderingBuilders; @override final Set joinBuilders; @@ -35,11 +70,14 @@ class ComposableOrdering implements HasJoinBuilders { joinBuilders.add(builder); } - /// Create a new ordering for a column + /// Create a new [ComposableOrdering] for a column without any joins ComposableOrdering.simple(this.orderingBuilders) : joinBuilders = {}; + + /// Create a new [ComposableOrdering] for a column with joins ComposableOrdering.withJoin(this.orderingBuilders, this.joinBuilders); - ComposableOrdering operator |(ComposableOrdering other) { + /// Combine two orderings with THEN + ComposableOrdering operator &(ComposableOrdering other) { return ComposableOrdering.withJoin( orderingBuilders.union(other.orderingBuilders), joinBuilders.union(other.joinBuilders)); @@ -52,12 +90,13 @@ class ComposableOrdering implements HasJoinBuilders { } /// The class that orchestrates the composition of orderings +/// +/// class OrderingComposer extends Composer { - /// Create a new ordering composer from existing query state - // OrderingComposer.fromComposer(super.state); - /// Create an ordering composer with an empty state OrderingComposer.empty(super.db, super.table) : super.empty(); + + /// Create an ordering composer using another composers state OrderingComposer.withAliasedTable(super.data) : super.withAliasedTable(); } diff --git a/drift_dev/lib/src/writer/manager.dart b/drift_dev/lib/src/writer/manager.dart index 141aef2c..b76a91c8 100644 --- a/drift_dev/lib/src/writer/manager.dart +++ b/drift_dev/lib/src/writer/manager.dart @@ -29,9 +29,7 @@ class ManagerWriter { filters.writeln( "ColumnFilters get $getterName => ColumnFilters(state.table.${col.nameInDart});"); orderings.writeln( - "ComposableOrdering get ${getterName}Asc => ComposableOrdering.simple({OrderingBuilder( OrderingMode.asc, state.table.${col.nameInDart})});"); - orderings.writeln( - "ComposableOrdering get ${getterName}Desc => ComposableOrdering.simple({OrderingBuilder( OrderingMode.desc, state.table.${col.nameInDart})});"); + "ColumnOrderings get $getterName => ColumnOrderings(state.table.${col.nameInDart});"); if (col.isForeignKey) { final referencedCol = col.constraints