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 /// Get a random alias for a table
String _getRandomAlias(TableInfo table) { String _getRandomAlias(TableInfo table) {
var aliasName = '${table.actualTableName}__${Random().nextInt(4294967296)}'; 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)}'; aliasName = '${table.actualTableName}__${Random().nextInt(4294967296)}';
continue; 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 @internal
sealed class Composer<DB extends GeneratedDatabase, CT extends Table> { 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; final ComposerState<DB, CT> state;
Composer.withAliasedTable(AliasedComposerBuilder<DB, dynamic, CT> data) 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); data.state.db, data.aliasedTable, data.state.joinBuilders);
Composer.empty(DB db, CT table) : state = ComposerState._(db, table, {}); Composer.empty(DB db, CT table) : state = ComposerState._(db, table, {});
/// Helper method for creaing an aliased join /// Method for create a join between two tables
/// and adding it to the state and Composable object
B referenced<RT extends Table, QC extends Composer<DB, RT>, B referenced<RT extends Table, QC extends Composer<DB, RT>,
B extends HasJoinBuilders>({ B extends HasJoinBuilders>({
required GeneratedColumn Function(CT) getCurrentColumn, 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 // that state doesnt have, it is also possible that the result is missing
// the `joinBuilder` we create above. // the `joinBuilder` we create above.
// We will combine both sets and set it to `state.joinBuilders` and `result.joinBuilders` // 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)) { for (var joinBuilder in result.joinBuilders.union(state.joinBuilders)) {
state.addJoinBuilder(joinBuilder); state.addJoinBuilder(joinBuilder);
result.addJoinBuilder(joinBuilder); result.addJoinBuilder(joinBuilder);

View File

@ -1,6 +1,6 @@
part of 'manager.dart'; 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> { class ColumnFilters<T extends Object> {
/// This class is a wrapper on top of the generated column class /// 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 /// Column that this [ColumnFilters] wraps
GeneratedColumn<T> column; GeneratedColumn<T> column;
// ignore: public_member_api_docs /// Create a filter that checks if the column is null.
ComposableFilter isNull() => ComposableFilter.simple(column.isNull()); 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()); 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 equals(T value) =>
ComposableFilter.simple(column.equals(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 /// Built in filters for int/double columns
extension NumFilters<T extends num> on ColumnFilters<T> { 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 isBiggerThan(T value) =>
ComposableFilter.simple(column.isBiggerThanValue(value)); ComposableFilter.simple(column.isBiggerThanValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotBiggerThan(T value) => isBiggerThan(value)._reversed(); /// Create a filter to check if the column is small than a value
// ignore: public_member_api_docs
ComposableFilter isSmallerThan(T value) => ComposableFilter isSmallerThan(T value) =>
ComposableFilter.simple(column.isSmallerThanValue(value)); ComposableFilter.simple(column.isSmallerThanValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotSmallerThan(T value) => /// Create a filter to check if the column is bigger or equal to a value
isSmallerThan(value)._reversed();
// ignore: public_member_api_docs
ComposableFilter isBiggerOrEqualTo(T value) => ComposableFilter isBiggerOrEqualTo(T value) =>
ComposableFilter.simple(column.isBiggerOrEqualValue(value)); ComposableFilter.simple(column.isBiggerOrEqualValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotBiggerOrEqualTo(T value) => /// Create a filter to check if the column is small or equal to a value
isBiggerOrEqualTo(value)._reversed();
// ignore: public_member_api_docs
ComposableFilter isSmallerOrEqualTo(T value) => ComposableFilter isSmallerOrEqualTo(T value) =>
ComposableFilter.simple(column.isSmallerOrEqualValue(value)); ComposableFilter.simple(column.isSmallerOrEqualValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotSmallerOrEqualTo(T value) => /// Create a filter to check if the column is between two values
isSmallerOrEqualTo(value)._reversed();
// ignore: public_member_api_docs
ComposableFilter isBetween(T lower, T higher) => ComposableFilter isBetween(T lower, T higher) =>
ComposableFilter.simple(column.isBetweenValues(lower, 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) => ComposableFilter isNotBetween(T lower, T higher) =>
isBetween(lower, higher)._reversed(); isBetween(lower, higher)._reversed();
} }
/// Built in filters for BigInt columns /// Built in filters for BigInt columns
extension BigIntFilters<T extends BigInt> on ColumnFilters<T> { 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 isBiggerThan(T value) =>
ComposableFilter.simple(column.isBiggerThanValue(value)); ComposableFilter.simple(column.isBiggerThanValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotBiggerThan(T value) => isBiggerThan(value)._reversed(); /// Create a filter to check if the column is small than a value
// ignore: public_member_api_docs
ComposableFilter isSmallerThan(T value) => ComposableFilter isSmallerThan(T value) =>
ComposableFilter.simple(column.isSmallerThanValue(value)); ComposableFilter.simple(column.isSmallerThanValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotSmallerThan(T value) => /// Create a filter to check if the column is bigger or equal to a value
isSmallerThan(value)._reversed();
// ignore: public_member_api_docs
ComposableFilter isBiggerOrEqualTo(T value) => ComposableFilter isBiggerOrEqualTo(T value) =>
ComposableFilter.simple(column.isBiggerOrEqualValue(value)); ComposableFilter.simple(column.isBiggerOrEqualValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotBiggerOrEqualTo(T value) => /// Create a filter to check if the column is small or equal to a value
isBiggerOrEqualTo(value)._reversed();
// ignore: public_member_api_docs
ComposableFilter isSmallerOrEqualTo(T value) => ComposableFilter isSmallerOrEqualTo(T value) =>
ComposableFilter.simple(column.isSmallerOrEqualValue(value)); ComposableFilter.simple(column.isSmallerOrEqualValue(value));
// ignore: public_member_api_docs
ComposableFilter isNotSmallerOrEqualTo(T value) => /// Create a filter to check if the column is between two values
isSmallerOrEqualTo(value)._reversed();
// ignore: public_member_api_docs
ComposableFilter isBetween(T lower, T higher) => ComposableFilter isBetween(T lower, T higher) =>
ComposableFilter.simple(column.isBetweenValues(lower, 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) => ComposableFilter isNotBetween(T lower, T higher) =>
isBetween(lower, higher)._reversed(); isBetween(lower, higher)._reversed();
} }
/// Built in filters for String columns /// Built in filters for String columns
extension DateFilters<T extends DateTime> on ColumnFilters<T> { 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 isAfter(T value) =>
ComposableFilter.simple(column.isBiggerThanValue(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 isBefore(T value) =>
ComposableFilter.simple(column.isSmallerThanValue(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 isAfterOrOn(T value) =>
ComposableFilter.simple(column.isBiggerOrEqualValue(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 isBeforeOrOn(T value) =>
ComposableFilter.simple(column.isSmallerOrEqualValue(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 isBetween(T lower, T higher) =>
ComposableFilter.simple(column.isBetweenValues(lower, 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) => ComposableFilter isNotBetween(T lower, T higher) =>
isBetween(lower, higher)._reversed(); isBetween(lower, higher)._reversed();
} }
@ -186,12 +174,11 @@ class ComposableFilter implements HasJoinBuilders {
} }
/// The class that orchestrates the composition of filtering /// 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> { extends Composer<DB, T> {
/// Create a new filter composer from existing query state
// FilterComposer(super.state);
/// Create a filter composer with an empty state /// Create a filter composer with an empty state
FilterComposer.empty(super.db, super.table) : super.empty(); FilterComposer.empty(super.db, super.table) : super.empty();
/// Create a filter composer using another composers state
FilterComposer.withAliasedTable(super.data) : super.withAliasedTable(); FilterComposer.withAliasedTable(super.data) : super.withAliasedTable();
} }

View File

@ -58,8 +58,3 @@ abstract interface class HasJoinBuilders {
/// Add a join builder to this class /// Add a join builder to this class
void addJoinBuilder(JoinBuilder builder); 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 'join.dart';
part 'ordering.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< class TableManagerState<
DB extends GeneratedDatabase, DB extends GeneratedDatabase,
T extends TableInfo, T extends Table,
DT extends DataClass, DT extends DataClass,
FS extends FilterComposer<DB, T>, FS extends FilterComposer<DB, T>,
OS extends OrderingComposer<DB, T>> { OS extends OrderingComposer<DB, T>> {
@ -23,10 +23,12 @@ class TableManagerState<
/// The expression that will be applied to the query /// The expression that will be applied to the query
final Expression<bool>? filter; 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; 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; final Set<JoinBuilder> joinBuilders;
/// Whether the query should return distinct results /// Whether the query should return distinct results
@ -38,15 +40,19 @@ class TableManagerState<
/// If set, the number of rows that will be skipped /// If set, the number of rows that will be skipped
final int? offset; 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; 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; final OS orderingComposer;
/// Defines a class which holds the state for a table manager /// 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 /// 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({ TableManagerState({
required this.db, required this.db,
required this.table, required this.table,
@ -88,7 +94,8 @@ class TableManagerState<
/// This is needed due to dart's limitations with generics /// This is needed due to dart's limitations with generics
TableInfo<T, DT> get _tableAsTableInfo => table as TableInfo<T, DT>; 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() { JoinedSelectStatement _buildJoinedSelectStatement() {
// Build the joins // Build the joins
final joins = joinBuilders.map((e) => e.buildJoin()).toList(); final joins = joinBuilders.map((e) => e.buildJoin()).toList();
@ -112,9 +119,10 @@ class TableManagerState<
return statement; 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() { SimpleSelectStatement<T, DT> _buildSimpleSelectStatement() {
// Create the joined statement // Create the statement
final statement = db.select(_tableAsTableInfo, distinct: distinct ?? false); final statement = db.select(_tableAsTableInfo, distinct: distinct ?? false);
// Apply the expression to the statement // Apply the expression to the statement

View File

@ -1,5 +1,35 @@
part of 'manager.dart'; 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 { class OrderingBuilder {
/// The mode of the ordering /// The mode of the ordering
final OrderingMode mode; final OrderingMode mode;
@ -7,6 +37,7 @@ class OrderingBuilder {
/// The column that the ordering is applied to /// The column that the ordering is applied to
final GeneratedColumn column; final GeneratedColumn column;
/// Create a new ordering builder, will be used by the [TableManagerState] to create [OrderingTerm]s
OrderingBuilder(this.mode, this.column); OrderingBuilder(this.mode, this.column);
@override @override
@ -26,7 +57,11 @@ class OrderingBuilder {
} }
/// Defines a class that can be used to compose orderings for a column /// 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 { class ComposableOrdering implements HasJoinBuilders {
/// The orderings that are being composed
final Set<OrderingBuilder> orderingBuilders; final Set<OrderingBuilder> orderingBuilders;
@override @override
final Set<JoinBuilder> joinBuilders; final Set<JoinBuilder> joinBuilders;
@ -35,11 +70,14 @@ class ComposableOrdering implements HasJoinBuilders {
joinBuilders.add(builder); 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 = {}; ComposableOrdering.simple(this.orderingBuilders) : joinBuilders = {};
/// Create a new [ComposableOrdering] for a column with joins
ComposableOrdering.withJoin(this.orderingBuilders, this.joinBuilders); ComposableOrdering.withJoin(this.orderingBuilders, this.joinBuilders);
ComposableOrdering operator |(ComposableOrdering other) { /// Combine two orderings with THEN
ComposableOrdering operator &(ComposableOrdering other) {
return ComposableOrdering.withJoin( return ComposableOrdering.withJoin(
orderingBuilders.union(other.orderingBuilders), orderingBuilders.union(other.orderingBuilders),
joinBuilders.union(other.joinBuilders)); joinBuilders.union(other.joinBuilders));
@ -52,12 +90,13 @@ class ComposableOrdering implements HasJoinBuilders {
} }
/// The class that orchestrates the composition of orderings /// The class that orchestrates the composition of orderings
///
///
class OrderingComposer<DB extends GeneratedDatabase, T extends Table> class OrderingComposer<DB extends GeneratedDatabase, T extends Table>
extends Composer<DB, T> { 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 /// Create an ordering composer with an empty state
OrderingComposer.empty(super.db, super.table) : super.empty(); OrderingComposer.empty(super.db, super.table) : super.empty();
/// Create an ordering composer using another composers state
OrderingComposer.withAliasedTable(super.data) : super.withAliasedTable(); OrderingComposer.withAliasedTable(super.data) : super.withAliasedTable();
} }

View File

@ -29,9 +29,7 @@ class ManagerWriter {
filters.writeln( filters.writeln(
"ColumnFilters get $getterName => ColumnFilters(state.table.${col.nameInDart});"); "ColumnFilters get $getterName => ColumnFilters(state.table.${col.nameInDart});");
orderings.writeln( orderings.writeln(
"ComposableOrdering get ${getterName}Asc => ComposableOrdering.simple({OrderingBuilder( OrderingMode.asc, state.table.${col.nameInDart})});"); "ColumnOrderings get $getterName => ColumnOrderings(state.table.${col.nameInDart});");
orderings.writeln(
"ComposableOrdering get ${getterName}Desc => ComposableOrdering.simple({OrderingBuilder( OrderingMode.desc, state.table.${col.nameInDart})});");
if (col.isForeignKey) { if (col.isForeignKey) {
final referencedCol = col.constraints final referencedCol = col.constraints