diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index 008d87ff..a2543f70 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.2 +## 2.0.2+1 - Revert the breaking change around `QueryRow.read` only returning non-nullable values now - it was causing issues with type inference in some cases. diff --git a/drift/lib/src/runtime/types/mapping.dart b/drift/lib/src/runtime/types/mapping.dart index bad4c789..4e3c9ea4 100644 --- a/drift/lib/src/runtime/types/mapping.dart +++ b/drift/lib/src/runtime/types/mapping.dart @@ -2,6 +2,7 @@ import 'dart:core'; import 'dart:core' as core; import 'dart:typed_data'; +import 'package:collection/collection.dart'; import 'package:convert/convert.dart'; import 'package:meta/meta.dart'; @@ -182,8 +183,19 @@ class SqlTypes { } } +/// In [DriftSqlType.forNullableType], we need to do an `is` check over +/// `DriftSqlType` with a potentially nullable `T`. Since `DriftSqlType` is +/// defined with a non-nullable `T`, this is illegal. +/// The non-nullable upper bound in [DriftSqlType] is generally useful, for +/// instance because it works well with [SqlTypes.read] which can then have a +/// sound nullable return type. +/// +/// As a hack, we define this base class that doesn't have this restriction and +/// use this one for type checks. +abstract class _InternalDriftSqlType {} + /// An enumation of type mappings that are builtin to drift and `drift_dev`. -enum DriftSqlType { +enum DriftSqlType implements _InternalDriftSqlType { /// A boolean type, represented as `0` or `1` (int) in SQL. bool(), @@ -282,12 +294,16 @@ enum DriftSqlType { /// Using [forType] should pretty much always be preferred over this method, /// this one just exists for backwards compatibility. static DriftSqlType forNullableType() { - final type = _dartToDrift[Dart]; + // Lookup the type in the map first for faster lookups. Go back to a full + // typecheck where that doesn't work (which can be the case for complex + // type like `forNullableType>`). + final type = _dartToDrift[Dart] ?? + values.whereType<_InternalDriftSqlType>().singleOrNull; if (type == null) { throw ArgumentError('Could not find a matching SQL type for $Dart'); } - return type; + return type as DriftSqlType; } } diff --git a/drift/pubspec.yaml b/drift/pubspec.yaml index c209bd24..b80880bf 100644 --- a/drift/pubspec.yaml +++ b/drift/pubspec.yaml @@ -1,6 +1,6 @@ name: drift description: Drift is a reactive library to store relational data in Dart and Flutter applications. -version: 2.0.2 +version: 2.0.2+1 repository: https://github.com/simolus3/drift homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/drift/issues diff --git a/drift/test/integration_tests/regress_1991_test.dart b/drift/test/integration_tests/regress_1991_test.dart new file mode 100644 index 00000000..51cb02b9 --- /dev/null +++ b/drift/test/integration_tests/regress_1991_test.dart @@ -0,0 +1,34 @@ +import 'package:drift/drift.dart' hide isNotNull, isNull; +import 'package:test/test.dart'; + +import '../generated/todos.dart'; +import '../test_utils/test_utils.dart'; + +// Regression test for https://github.com/simolus3/drift/issues/1991 + +Future _getCategoryIdByDescription( + TodoDb appDatabase, String description) async { + const q = "SELECT id FROM categories WHERE desc = ?"; + final row = await appDatabase.customSelect( + q, + variables: [Variable(description)], + ).getSingleOrNull(); + return row?.read("id"); +} + +void main() { + test('type inference for nullable call in async function', () async { + final db = TodoDb.connect(testInMemoryDatabase()); + addTearDown(db.close); + + final categoryDescription = 'category description'; + expect(await _getCategoryIdByDescription(db, categoryDescription), isNull); + + await db.categories.insertOne( + CategoriesCompanion.insert(description: categoryDescription)); + + // Search the category we just inserted + expect( + await _getCategoryIdByDescription(db, categoryDescription), isNotNull); + }); +}