diff --git a/drift/lib/src/runtime/manager/filter.dart b/drift/lib/src/runtime/manager/filter.dart index 26e17e2d..9827be99 100644 --- a/drift/lib/src/runtime/manager/filter.dart +++ b/drift/lib/src/runtime/manager/filter.dart @@ -9,35 +9,44 @@ class ColumnFilters { /// Use an extention to add more filters to any column type /// /// ```dart - /// extension on FilterComposer{ - /// FitlerBuilder after2000() => isAfter(DateTime(2000)); + /// extension on ColumnFilters{ + /// ComposableFilter after2000() => isAfter(DateTime(2000)); ///} /// ``` - const ColumnFilters(this.column); + const ColumnFilters(this.column, [this.inverted = false]); /// Column that this [ColumnFilters] wraps final Expression column; - /// Create a filter that checks if the column is null. - ComposableFilter isNull() => ComposableFilter(column.isNull()); + /// If true, all filters will be inverted + final bool inverted; - /// Create a filter that checks if the column is not null. - ComposableFilter isNotNull() => ComposableFilter(column.isNotNull()); + /// Returns a copy of these column filters where all the filters are inverted + /// ```dart + /// myColumn.not.equals(5); // All columns that aren't null and have a value that is not equal to 5 + /// ``` + /// Keep in mind that while using inverted filters, null is never returned. + /// + /// If you would like to include them, use the [isNull] filter as well + /// ```dart + /// myColumn.not.equals(5) | myColumn.isNull(); // All columns that are null OR have a value that is not equal to 5 will be returned + /// ``` + ColumnFilters get not => ColumnFilters(column, !inverted); /// Create a filter that checks if the column equals a value. - ComposableFilter equals(T value) => ComposableFilter(column.equals(value)); + ComposableFilter equals(T value) => + ComposableFilter(column.equals(value), inverted: inverted); + + /// Create a filter that checks if the column is null. + ComposableFilter isNull() => + ComposableFilter(column.isNull(), inverted: inverted); /// Create a filter that checks if the column is in a list of values. - ComposableFilter isIn(Iterable values) => - ComposableFilter(column.isIn(values)); - - /// Create a filter that checks if the column is not in a list of values. - ComposableFilter isNotIn(Iterable values) => - ComposableFilter(column.isNotIn(values)); + ComposableFilter(column.isIn(values), inverted: inverted); /// Shortcut for [equals] - ComposableFilter call(T value) => ComposableFilter(column.equals(value)); + ComposableFilter call(T value) => equals(value); } enum _StringFilterTypes { contains, startsWith, endsWith } @@ -76,165 +85,191 @@ extension StringFilters on ColumnFilters { } /// Create a filter to check if the this text column contains a substring - /// {@template drift.manager.likeCaseSensitivity} /// /// Setting [caseInsensitive] to false will have no effect unless the database in configured to use /// case sensitive like expressions. /// /// See https://www.sqlitetutorial.net/sqlite-like/ for more information on how /// to the like expression works. - /// {@endtemplate} ComposableFilter contains(T value, {bool caseInsensitive = true}) { return ComposableFilter( - _buildExpression(_StringFilterTypes.contains, value, caseInsensitive)); + _buildExpression(_StringFilterTypes.contains, value, caseInsensitive), + inverted: inverted); } /// Create a filter to check if the this text column starts with a substring - /// {@macro drift.manager.likeCaseSensitivity} + /// + /// Setting [caseInsensitive] to false will have no effect unless the database in configured to use + /// case sensitive like expressions. + /// + /// See https://www.sqlitetutorial.net/sqlite-like/ for more information on how + /// to the like expression works. ComposableFilter startsWith(T value, {bool caseInsensitive = true}) { - return ComposableFilter(_buildExpression( - _StringFilterTypes.startsWith, value, caseInsensitive)); + return ComposableFilter( + _buildExpression(_StringFilterTypes.startsWith, value, caseInsensitive), + inverted: inverted); } /// Create a filter to check if the this text column ends with a substring - /// {@macro drift.manager.likeCaseSensitivity} + /// + /// Setting [caseInsensitive] to false will have no effect unless the database in configured to use + /// case sensitive like expressions. + /// + /// See https://www.sqlitetutorial.net/sqlite-like/ for more information on how + /// to the like expression works. ComposableFilter endsWith(T value, {bool caseInsensitive = true}) { return ComposableFilter( - _buildExpression(_StringFilterTypes.endsWith, value, caseInsensitive)); + _buildExpression(_StringFilterTypes.endsWith, value, caseInsensitive), + inverted: inverted); } } /// Built in filters for bool columns extension BoolFilters on ColumnFilters { /// Create a filter to check if the column is bigger than a value - ComposableFilter isTrue() => ComposableFilter(column.equals(true)); + ComposableFilter isTrue() => + ComposableFilter(column.equals(true), inverted: inverted); /// Create a filter to check if the column is small than a value - ComposableFilter isFalse() => ComposableFilter(column.equals(false)); + ComposableFilter isFalse() => + ComposableFilter(column.equals(false), inverted: inverted); } /// Built in filters for int/double columns extension NumFilters on ColumnFilters { /// Create a filter to check if the column is bigger than a value ComposableFilter isBiggerThan(T value) => - ComposableFilter(column.isBiggerThanValue(value)); + ComposableFilter(column.isBiggerThanValue(value), inverted: inverted); /// Create a filter to check if the column is small than a value ComposableFilter isSmallerThan(T value) => - ComposableFilter(column.isSmallerThanValue(value)); + ComposableFilter(column.isSmallerThanValue(value), inverted: inverted); /// Create a filter to check if the column is bigger or equal to a value ComposableFilter isBiggerOrEqualTo(T value) => - ComposableFilter(column.isBiggerOrEqualValue(value)); + ComposableFilter(column.isBiggerOrEqualValue(value), inverted: inverted); /// Create a filter to check if the column is small or equal to a value ComposableFilter isSmallerOrEqualTo(T value) => - ComposableFilter(column.isSmallerOrEqualValue(value)); + ComposableFilter(column.isSmallerOrEqualValue(value), inverted: inverted); /// Create a filter to check if the column is between two values /// This is done inclusively, so the column can be equal to the lower or higher value /// E.G isBetween(1, 3) will return true for 1, 2, and 3 ComposableFilter isBetween(T lower, T higher) => - ComposableFilter(column.isBetweenValues(lower, higher)); - - /// Create a filter to check if the column is not between two values - /// This is done inclusively, so the column will not be equal to the lower or higher value - /// E.G isNotBetween(1, 3) will return false for 1, 2, and 3 - ComposableFilter isNotBetween(T lower, T higher) => - isBetween(lower, higher)._invert(); + ComposableFilter(column.isBetweenValues(lower, higher), + inverted: inverted); } /// Built in filters for BigInt columns extension BigIntFilters on ColumnFilters { /// Create a filter to check if the column is bigger than a value ComposableFilter isBiggerThan(T value) => - ComposableFilter(column.isBiggerThanValue(value)); + ComposableFilter(column.isBiggerThanValue(value), inverted: inverted); /// Create a filter to check if the column is small than a value ComposableFilter isSmallerThan(T value) => - ComposableFilter(column.isSmallerThanValue(value)); + ComposableFilter(column.isSmallerThanValue(value), inverted: inverted); /// Create a filter to check if the column is bigger or equal to a value ComposableFilter isBiggerOrEqualTo(T value) => - ComposableFilter(column.isBiggerOrEqualValue(value)); + ComposableFilter(column.isBiggerOrEqualValue(value), inverted: inverted); /// Create a filter to check if the column is small or equal to a value ComposableFilter isSmallerOrEqualTo(T value) => - ComposableFilter(column.isSmallerOrEqualValue(value)); + ComposableFilter(column.isSmallerOrEqualValue(value), inverted: inverted); /// Create a filter to check if the column is between two values /// This is done inclusively, so the column can be equal to the lower or higher value /// E.G isBetween(1, 3) will return true for 1, 2, and 3 ComposableFilter isBetween(T lower, T higher) => - ComposableFilter(column.isBetweenValues(lower, higher)); - - /// Create a filter to check if the column is not between two values - /// This is done inclusively, so the column will not be equal to the lower or higher value - /// E.G isNotBetween(1, 3) will return false for 1, 2, and 3 - ComposableFilter isNotBetween(T lower, T higher) => - isBetween(lower, higher)._invert(); + ComposableFilter(column.isBetweenValues(lower, higher), + inverted: inverted); } /// Built in filters for DateTime columns extension DateFilters on ColumnFilters { /// Create a filter to check if the column is after a [DateTime] ComposableFilter isAfter(T value) => - ComposableFilter(column.isBiggerThanValue(value)); + ComposableFilter(column.isBiggerThanValue(value), inverted: inverted); /// Create a filter to check if the column is before a [DateTime] ComposableFilter isBefore(T value) => - ComposableFilter(column.isSmallerThanValue(value)); + ComposableFilter(column.isSmallerThanValue(value), inverted: inverted); /// Create a filter to check if the column is on or after a [DateTime] ComposableFilter isAfterOrOn(T value) => - ComposableFilter(column.isBiggerOrEqualValue(value)); + ComposableFilter(column.isBiggerOrEqualValue(value), inverted: inverted); /// Create a filter to check if the column is before or on a [DateTime] ComposableFilter isBeforeOrOn(T value) => - ComposableFilter(column.isSmallerOrEqualValue(value)); + ComposableFilter(column.isSmallerOrEqualValue(value), inverted: inverted); /// Create a filter to check if the column is between 2 [DateTime]s /// This is done inclusively, so the column can be equal to the lower or higher value /// E.G isBetween(1, 3) will return true for 1, 2, and 3 ComposableFilter isBetween(T lower, T higher) => - ComposableFilter(column.isBetweenValues(lower, higher)); - - /// Create a filter to check if the column is not between 2 [DateTime]s - /// This is done inclusively, so the column will not be equal to the lower or higher value - /// E.G isNotBetween(1, 3) will return false for 1, 2, and 3 - ComposableFilter isNotBetween(T lower, T higher) => - isBetween(lower, higher)._invert(); + ComposableFilter(column.isBetweenValues(lower, higher), + inverted: inverted); } /// Defines a class which is used to wrap a column with a type converter to only expose filter functions -class ColumnWithTypeConverterFilters { +// [CustomType] is the type that the user has defined in their type converter +// [CustomTypeNonNullable] is the type that the user has defined in their type converter, but is non-nullable +class ColumnWithTypeConverterFilters { /// Similar to [ColumnFilters] but for columns with type converters\ - const ColumnWithTypeConverterFilters(this.column); + const ColumnWithTypeConverterFilters(this.column, [this.inverted = false]); + + /// If true, all filters will be inverted + final bool inverted; /// Column that this [ColumnWithTypeConverterFilters] wraps - final GeneratedColumnWithTypeConverter column; + final GeneratedColumnWithTypeConverter column; + + /// Returns a copy of these column filters where all the filters are inverted + /// ```dart + /// myColumn.not.equals(5); // All columns that aren't null and have a value that is not equal to 5 + /// ``` + /// Keep in mind that while using inverted filters, null is never returned. + /// + /// If you would like to include them, use the [isNull] filter as well + /// ```dart + /// myColumn.not.equals(5) | myColumn.isNull(); // All columns that are null OR have a value that is not equal to 5 + /// ``` + ColumnWithTypeConverterFilters + get not => ColumnWithTypeConverterFilters(column, !inverted); /// Create a filter that checks if the column is null. - ComposableFilter isNull() => ComposableFilter(column.isNull()); + ComposableFilter isNull() => + ComposableFilter(column.isNull(), inverted: inverted); - /// Create a filter that checks if the column is not null. - ComposableFilter isNotNull() => ComposableFilter(column.isNotNull()); + /// Get the actual value from the custom type + T _customTypeToSql(CustomTypeNonNullable value) { + assert(value != null, + 'The filter value cannot be null. This is likely a bug in the generated code. Please report this issue.'); + final mappedValue = column.converter.toSql(value as CustomType); + + if (mappedValue == null) { + throw ArgumentError( + 'The TypeConverter for this column returned null when converting the type to sql. Ensure that your TypeConverter never returns null when provided a non-null value.'); + } + return mappedValue; + } /// Create a filter that checks if the column equals a value. - ComposableFilter equals(CUSTOM value) => - ComposableFilter(column.equalsValue(value)); + ComposableFilter equals(CustomTypeNonNullable value) { + return ComposableFilter(column.equals(_customTypeToSql(value)), + inverted: inverted); + } /// Shortcut for [equals] - ComposableFilter call(CUSTOM value) => - ComposableFilter(column.equalsValue(value)); + ComposableFilter call(CustomTypeNonNullable value) => equals(value); /// Create a filter that checks if the column is in a list of values. - ComposableFilter isIn(Iterable values) => - ComposableFilter(column.isInValues(values)); - - /// Create a filter that checks if the column is not in a list of values. - ComposableFilter isNotIn(Iterable values) => - ComposableFilter(column.isNotInValues(values)); + ComposableFilter isIn(Iterable values) => + ComposableFilter(column.isIn(values.map(_customTypeToSql).toList()), + inverted: inverted); } /// This class is wrapper on the expression class @@ -247,10 +282,13 @@ class ComposableFilter extends HasJoinBuilders { final Set joinBuilders; /// The expression that will be applied to the query - final Expression expression; + late final Expression expression; /// Create a new [ComposableFilter] for a column without any joins - ComposableFilter(this.expression) : joinBuilders = {}; + ComposableFilter(Expression expression, {required bool inverted}) + : joinBuilders = {} { + this.expression = inverted ? expression.not() : expression; + } /// Create a new [ComposableFilter] for a column with joins ComposableFilter._(this.expression, this.joinBuilders); @@ -270,14 +308,6 @@ class ComposableFilter extends HasJoinBuilders { joinBuilders.union(other.joinBuilders), ); } - - /// Returns a copy of this filter with the expression reversed - ComposableFilter _invert() { - return ComposableFilter._( - expression.not(), - joinBuilders, - ); - } } /// The class that orchestrates the composition of filtering diff --git a/drift/test/generated/custom_tables.g.dart b/drift/test/generated/custom_tables.g.dart index f8bbe29f..51c53c4f 100644 --- a/drift/test/generated/custom_tables.g.dart +++ b/drift/test/generated/custom_tables.g.dart @@ -2139,12 +2139,13 @@ class $ConfigTableFilterComposer ColumnFilters get configKey => ColumnFilters($table.configKey); ColumnFilters get configValue => ColumnFilters($table.configValue); ColumnFilters get syncStateValue => ColumnFilters($table.syncState); - ColumnWithTypeConverterFilters get syncState => + ColumnWithTypeConverterFilters get syncState => ColumnWithTypeConverterFilters($table.syncState); ColumnFilters get syncStateImplicitValue => ColumnFilters($table.syncStateImplicit); - ColumnWithTypeConverterFilters get syncStateImplicit => - ColumnWithTypeConverterFilters($table.syncStateImplicit); + ColumnWithTypeConverterFilters + get syncStateImplicit => + ColumnWithTypeConverterFilters($table.syncStateImplicit); } class $ConfigTableOrderingComposer diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index 233e26b1..0847a5c8 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -2502,12 +2502,12 @@ class $$CategoriesTableFilterComposer extends FilterComposer<_$TodoDb, $CategoriesTable> { $$CategoriesTableFilterComposer(super.db, super.table); ColumnFilters get idValue => ColumnFilters($table.id); - ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters get id => ColumnWithTypeConverterFilters($table.id); ColumnFilters get description => ColumnFilters($table.description); ColumnFilters get priorityValue => ColumnFilters($table.priority); - ColumnWithTypeConverterFilters get priority => - ColumnWithTypeConverterFilters($table.priority); + ColumnWithTypeConverterFilters + get priority => ColumnWithTypeConverterFilters($table.priority); ColumnFilters get descriptionInUpperCase => ColumnFilters($table.descriptionInUpperCase); ComposableFilter todosTableRefs( @@ -2600,7 +2600,7 @@ class $$TodosTableTableFilterComposer extends FilterComposer<_$TodoDb, $TodosTableTable> { $$TodosTableTableFilterComposer(super.db, super.table); ColumnFilters get idValue => ColumnFilters($table.id); - ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters get id => ColumnWithTypeConverterFilters($table.id); ColumnFilters get title => ColumnFilters($table.title); ColumnFilters get content => ColumnFilters($table.content); @@ -2620,7 +2620,7 @@ class $$TodosTableTableFilterComposer } ColumnFilters get statusValue => ColumnFilters($table.status); - ColumnWithTypeConverterFilters get status => + ColumnWithTypeConverterFilters get status => ColumnWithTypeConverterFilters($table.status); } @@ -2731,7 +2731,7 @@ class $$TodosTableTableTableManager extends RootTableManager< class $$UsersTableFilterComposer extends FilterComposer<_$TodoDb, $UsersTable> { $$UsersTableFilterComposer(super.db, super.table); ColumnFilters get idValue => ColumnFilters($table.id); - ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters get id => ColumnWithTypeConverterFilters($table.id); ColumnFilters get name => ColumnFilters($table.name); ColumnFilters get isAwesome => ColumnFilters($table.isAwesome); @@ -2907,8 +2907,8 @@ class $$PureDefaultsTableFilterComposer extends FilterComposer<_$TodoDb, $PureDefaultsTable> { $$PureDefaultsTableFilterComposer(super.db, super.table); ColumnFilters get txtValue => ColumnFilters($table.txt); - ColumnWithTypeConverterFilters get txt => - ColumnWithTypeConverterFilters($table.txt); + ColumnWithTypeConverterFilters + get txt => ColumnWithTypeConverterFilters($table.txt); } class $$PureDefaultsTableOrderingComposer @@ -3049,7 +3049,7 @@ class $$TableWithEveryColumnTypeTableFilterComposer extends FilterComposer<_$TodoDb, $TableWithEveryColumnTypeTable> { $$TableWithEveryColumnTypeTableFilterComposer(super.db, super.table); ColumnFilters get idValue => ColumnFilters($table.id); - ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters get id => ColumnWithTypeConverterFilters($table.id); ColumnFilters get aBool => ColumnFilters($table.aBool); ColumnFilters get aDateTime => ColumnFilters($table.aDateTime); @@ -3059,11 +3059,11 @@ class $$TableWithEveryColumnTypeTableFilterComposer ColumnFilters get aReal => ColumnFilters($table.aReal); ColumnFilters get aBlob => ColumnFilters($table.aBlob); ColumnFilters get anIntEnumValue => ColumnFilters($table.anIntEnum); - ColumnWithTypeConverterFilters get anIntEnum => + ColumnWithTypeConverterFilters get anIntEnum => ColumnWithTypeConverterFilters($table.anIntEnum); ColumnFilters get aTextWithConverterValue => ColumnFilters($table.aTextWithConverter); - ColumnWithTypeConverterFilters + ColumnWithTypeConverterFilters get aTextWithConverter => ColumnWithTypeConverterFilters($table.aTextWithConverter); } diff --git a/drift/test/manager/manager_filter_tests.dart b/drift/test/manager/manager_filter_tests.dart index 925bdb96..7b0be64e 100644 --- a/drift/test/manager/manager_filter_tests.dart +++ b/drift/test/manager/manager_filter_tests.dart @@ -5,134 +5,12 @@ import '../generated/todos.dart'; import '../test_utils/test_utils.dart'; void main() { - driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; late TodoDb db; setUp(() { db = TodoDb(testInMemoryDatabase()); }); - // // Create a dataset for the tests - // schoolId = await db.managers.categories.create((o) => - // o(priority: Value(CategoryPriority.high), description: "School")); - // workId = await db.managers.categories.create( - // (o) => o(priority: Value(CategoryPriority.low), description: "Work")); - // homeId = await db.managers.categories.create((o) => - // o(priority: Value(CategoryPriority.medium), description: "Home")); - // otherId = await db.managers.categories.create( - // (o) => o(priority: Value(CategoryPriority.high), description: "Other")); - - // // School - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: Value("Get that math homework done"), - // title: Value("Math Homework"), - // category: Value(schoolId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1,seconds: 10))))); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Finish that report", - // title: Value("Report"), - // category: Value(workId), - // anIntEnum: Value(TodoStatus.workInProgress), - // aDateTime: Value(DateTime.now().add(Duration(days: 2,seconds: 10))))); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Get that english homework done", - // title: Value("English Homework"), - // category: Value(schoolId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1,seconds: 15))))); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Finish that Book report", - // title: Value("Book Report"), - // category: Value(workId), - // anIntEnum: Value(TodoStatus.done), - // aDateTime: Value(DateTime.now().subtract(Duration(days: 2,seconds: 15))))); - - // // Work - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "File those reports", - // title: Value("File Reports"), - // category: Value(workId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1, seconds: 20))));); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Clean the office", - // title: Value("Clean Office"), - // category: Value(workId), - // anIntEnum: Value(TodoStatus.workInProgress), - // aDateTime: Value(DateTime.now().add(Duration(days: 2, seconds: 20))));); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Nail that presentation", - // title: Value("Presentation"), - // category: Value(workId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1, seconds: 25))))); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Take a break", - // title: Value("Break"), - // category: Value(workId), - // anIntEnum: Value(TodoStatus.done), - // aDateTime: Value(DateTime.now().subtract(Duration(days: 2, seconds: 25))))); - - // // Work - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Take out the trash", - // title: Value("Trash"), - // category: Value(homeId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1, seconds: 30))));); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Mow the lawn", - // title: Value("Lawn"), - // category: Value(homeId), - // anIntEnum: Value(TodoStatus.workInProgress), - // aDateTime: Value(DateTime.now().add(Duration(days: 2, seconds: 30))))); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Fix the sink", - // title: Value("Sink"), - // category: Value(homeId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1, seconds: 35))));); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Paint the walls", - // title: Value("Paint"), - // category: Value(homeId), - // anIntEnum: Value(TodoStatus.done), - // aDateTime: Value(DateTime.now().subtract(Duration(days: 2, seconds: 35))))); - - // // Other - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Get groceries", - // title: Value("Groceries"), - // category: Value(otherId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1, seconds: 40))));); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Pick up the kids", - // title: Value("Kids"), - // category: Value(otherId), - // anIntEnum: Value(TodoStatus.workInProgress), - // aDateTime: Value(DateTime.now().add(Duration(days: 2, seconds: 40))));); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Take the dog for a walk", - // title: Value("Dog"), - // category: Value(otherId), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1, seconds: 45))))); - - // // Items with no category - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Get Whiteboard", - // title: Value("Whiteboard"), - // anIntEnum: Value(TodoStatus.open), - // aDateTime: Value(DateTime.now().add(Duration(days: 1, seconds: 50))));); - // await db.managers.tableWithEveryColumnType.create((o) => o( - // aText: "Drink Water", - // title: Value("Water"), - // anIntEnum: Value(TodoStatus.workInProgress), - // aDateTime: Value(DateTime.now().add(Duration(days: 2, seconds: 50))))); - // }); - tearDown(() => db.close()); test('manager - query generic', () async { @@ -162,6 +40,14 @@ void main() { .filter((f) => f.aReal(3.0)) .count(), completion(1)); + + // Not Equals - Exclude null (Default behavior) + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.not.equals(5.0)) + .count(), + completion(1)); + // In expect( db.managers.tableWithEveryColumnType @@ -172,7 +58,7 @@ void main() { // Not In expect( db.managers.tableWithEveryColumnType - .filter((f) => f.aReal.isNotIn([3.0, 5.0])) + .filter((f) => f.aReal.not.isIn([3.0, 5.0])) .count(), completion(0)); @@ -182,9 +68,10 @@ void main() { .filter((f) => f.aReal.isNull()) .count(), completion(1)); + expect( db.managers.tableWithEveryColumnType - .filter((f) => f.aReal.isNotNull()) + .filter((f) => f.aReal.not.isNull()) .count(), completion(2)); }); @@ -237,7 +124,7 @@ void main() { completion(2)); expect( db.managers.tableWithEveryColumnType - .filter((f) => f.aReal.isNotBetween(3.0, 5.0)) + .filter((f) => f.aReal.not.isBetween(3.0, 5.0)) .count(), completion(0)); }); @@ -351,7 +238,7 @@ void main() { expect( db.managers.tableWithEveryColumnType .filter((f) => - f.anInt64.isNotBetween(BigInt.from(3.0), BigInt.from(5.0))) + f.anInt64.not.isBetween(BigInt.from(3.0), BigInt.from(5.0))) .count(), completion(0)); }); @@ -432,7 +319,7 @@ void main() { completion(2)); expect( db.managers.tableWithEveryColumnType - .filter((f) => f.aDateTime.isNotBetween(day1, day2)) + .filter((f) => f.aDateTime.not.isBetween(day1, day2)) .count(), completion(1)); }); @@ -450,6 +337,8 @@ void main() { await db.managers.tableWithEveryColumnType.create((o) => o( aText: Value("Get that math homework done"), anIntEnum: Value(TodoStatus.done))); + await db.managers.tableWithEveryColumnType + .create((o) => o(aText: Value("Get that math homework done"))); // Equals expect( @@ -463,20 +352,172 @@ void main() { .count(), completion(2)); - // In expect( db.managers.tableWithEveryColumnType - .filter((f) => - f.anIntEnum.isIn([TodoStatus.open, TodoStatus.workInProgress])) + .filter((f) => f.anIntEnum.not(TodoStatus.open)) .count(), - completion(3)); + completion(2)); - // Not In + // // Not Equals + // expect( + // db.managers.tableWithEveryColumnType + // .filter((f) => f.anIntEnum.not.equals(TodoStatus.open)) + // .count(), + // completion(2)); + + // // In + // expect( + // db.managers.tableWithEveryColumnType + // .filter((f) => + // f.anIntEnum.isIn([TodoStatus.open, TodoStatus.workInProgress])) + // .count(), + // completion(3)); + + // // Not In + // expect( + // db.managers.tableWithEveryColumnType + // .filter((f) => f.anIntEnum.not + // .isIn([TodoStatus.open, TodoStatus.workInProgress])) + // .count(), + // completion(1)); + }); + + test('manager - filter related', () async { + final schoolCategoryId = await db.managers.categories.create((o) => + o(priority: Value(CategoryPriority.high), description: "School")); + final workCategoryId = await db.managers.categories.create( + (o) => o(priority: Value(CategoryPriority.low), description: "Work")); + + // School + await db.managers.todosTable.create((o) => o( + content: "Get that math homework done", + title: Value("Math Homework"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 10))))); + await db.managers.todosTable.create((o) => o( + content: "Finish that report", + title: Value("Report"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.workInProgress), + targetDate: Value(DateTime.now().add(Duration(days: 2, seconds: 10))))); + await db.managers.todosTable.create((o) => o( + content: "Get that english homework done", + title: Value("English Homework"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 15))))); + await db.managers.todosTable.create((o) => o( + content: "Finish that Book report", + title: Value("Book Report"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.done), + targetDate: + Value(DateTime.now().subtract(Duration(days: 2, seconds: 15))))); + + // Work + await db.managers.todosTable.create((o) => o( + content: "File those reports", + title: Value("File Reports"), + category: Value(workCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 20))))); + await db.managers.todosTable.create((o) => o( + content: "Clean the office", + title: Value("Clean Office"), + category: Value(workCategoryId), + status: Value(TodoStatus.workInProgress), + targetDate: Value(DateTime.now().add(Duration(days: 2, seconds: 20))))); + await db.managers.todosTable.create((o) => o( + content: "Nail that presentation", + title: Value("Presentation"), + category: Value(workCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 25))))); + await db.managers.todosTable.create((o) => o( + content: "Take a break", + title: Value("Break"), + category: Value(workCategoryId), + status: Value(TodoStatus.done), + targetDate: + Value(DateTime.now().subtract(Duration(days: 2, seconds: 25))))); + + // Items with no category + await db.managers.todosTable.create((o) => o( + content: "Get Whiteboard", + title: Value("Whiteboard"), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 50))))); + await db.managers.todosTable.create((o) => o( + content: "Drink Water", + title: Value("Water"), + status: Value(TodoStatus.workInProgress), + targetDate: Value(DateTime.now().add(Duration(days: 2, seconds: 50))))); + + // item without title + + // Equals expect( - db.managers.tableWithEveryColumnType - .filter((f) => f.anIntEnum - .isNotIn([TodoStatus.open, TodoStatus.workInProgress])) + db.managers.todosTable + .filter((f) => f.category( + (f) => f.id(RowId(schoolCategoryId)), + )) .count(), - completion(1)); + completion(4)); + + // Not Equals + expect( + db.managers.todosTable + .filter( + (f) => + f.category((f) => f.id.not.equals(RowId(schoolCategoryId))), + ) + .count(), + completion(4)); + + // Multiple filters + expect( + db.managers.todosTable + .filter((f) => f.category( + (f) => f.id(RowId(schoolCategoryId)), + )) + .filter((f) => f.status.equals(TodoStatus.open)) + .count(), + completion(2)); + + // Multiple 2 related filters + expect( + db.managers.todosTable + .filter((f) => f.category( + (f) => + f.priority.equals(CategoryPriority.low) | + f.descriptionInUpperCase.equals("SCHOOL"), + )) + .count(), + completion(8)); + + // Multiple use related filters twice + expect( + db.managers.todosTable + .filter((f) => + f.category( + (f) => f.priority.equals(CategoryPriority.low), + ) | + f.category( + (f) => f.descriptionInUpperCase.equals("SCHOOL"), + )) + .count(), + completion(8)); + // Use .filter multiple times + expect( + db.managers.todosTable + .filter((f) => f.category( + (f) => f.priority.equals(CategoryPriority.high), + )) + .filter((f) => f.category( + (f) => f.descriptionInUpperCase.equals("SCHOOL"), + )) + .count(), + completion(4)); }); } diff --git a/drift_dev/lib/src/writer/manager_writer.dart b/drift_dev/lib/src/writer/manager_writer.dart index 67cf6b60..9ccd2a1e 100644 --- a/drift_dev/lib/src/writer/manager_writer.dart +++ b/drift_dev/lib/src/writer/manager_writer.dart @@ -61,9 +61,11 @@ class _FilterWithConverterWriter extends _FilterWriter { @override void writeFilter(TextEmitter leaf) { + final nonNullableConverterType = converterType.replaceFirst("?", ""); leaf ..writeDriftRef("ColumnWithTypeConverterFilters") - ..write("<$converterType,$type> get $filterName =>") + ..write( + "<$converterType,$nonNullableConverterType,$type> get $filterName =>") ..writeDriftRef("ColumnWithTypeConverterFilters") ..writeln("(\$table.$fieldGetter);"); }