Refactor ordering - Add better Docs

This commit is contained in:
Moshe Dicker 2024-03-29 10:44:59 -04:00
parent 1da0664c08
commit 46953c3a73
6 changed files with 126 additions and 87 deletions

View File

@ -21,7 +21,7 @@ class ComposerState<DB extends GeneratedDatabase, T extends Table>
/// 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<DB extends GeneratedDatabase, RT extends Table,
);
}
/// Base class for all query composers
/// Base class for all composers
///
/// Any class that can be composed using the `&` or `|` operator is called a composable,
/// and must implement the [HasJoinBuilders] interface. [ComposableFilter] and [ComposableOrdering] are examples of composable classes.
///
/// The [Composer] class is a top level manager for this operation.
/// ```dart
/// filter((f) => 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<DB extends GeneratedDatabase, CT extends Table> {
/// The state of the query composer
/// The state of the composer
final ComposerState<DB, CT> state;
Composer.withAliasedTable(AliasedComposerBuilder<DB, dynamic, CT> data)
@ -55,9 +71,7 @@ sealed class Composer<DB extends GeneratedDatabase, CT extends Table> {
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<RT extends Table, QC extends Composer<DB, RT>,
B extends HasJoinBuilders>({
required GeneratedColumn Function(CT) getCurrentColumn,
@ -107,8 +121,6 @@ sealed class Composer<DB extends GeneratedDatabase, CT extends Table> {
// 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);

View File

@ -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<T extends Object> {
/// This class is a wrapper on top of the generated column class
///
@ -18,111 +18,99 @@ class ColumnFilters<T extends Object> {
/// Column that this [ColumnFilters] wraps
GeneratedColumn<T> 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<T extends num> on ColumnFilters<T> {
// 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<T extends BigInt> on ColumnFilters<T> {
// 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<T extends DateTime> on ColumnFilters<T> {
// 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<DB extends GeneratedDatabase, T extends TableInfo>
class FilterComposer<DB extends GeneratedDatabase, T extends Table>
extends Composer<DB, T> {
/// 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();
}

View File

@ -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<JoinBuilder> {
List<String> get aliasedNames => map((e) => (e.aliasedName)).toList();
}

View File

@ -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<DB, T>,
OS extends OrderingComposer<DB, T>> {
@ -23,10 +23,12 @@ class TableManagerState<
/// The expression that will be applied to the query
final Expression<bool>? 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<OrderingBuilder> 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<JoinBuilder> 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<T, DT> get _tableAsTableInfo => table as TableInfo<T, DT>;
/// 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<T, DT> _buildSimpleSelectStatement() {
// Create the joined statement
// Create the statement
final statement = db.select(_tableAsTableInfo, distinct: distinct ?? false);
// Apply the expression to the statement

View File

@ -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<T extends Object> {
/// 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<DateTime>{
/// FitlerBuilder after2000() => isAfter(DateTime(2000));
///}
/// ```
ColumnOrderings(this.column);
/// Column that this [ColumnOrderings] wraps
GeneratedColumn<T> 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<OrderingBuilder> orderingBuilders;
@override
final Set<JoinBuilder> 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<DB extends GeneratedDatabase, T extends Table>
extends Composer<DB, T> {
/// 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();
}

View File

@ -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