diff --git a/moor/CHANGELOG.md b/moor/CHANGELOG.md index 2b15c930..a3f5afbb 100644 --- a/moor/CHANGELOG.md +++ b/moor/CHANGELOG.md @@ -5,6 +5,8 @@ - __Breaking__: Removed `QueryEngine`, its methods have been moved to `DatabaseConnectionUser` - __Breaking__: Changed the `args` parameter in `QueryExecutor` methods to `List` - __Breaking__: Removed the second type parameter from `TypedResult.read` +- __Breaking__: `TypedResult.read` and `TypedResult.readTable` now throw if the row does not contain + the requested table or expression (they used to return `null`). - __Breaking__: `MoorWebStorage.indexedDbIfSupported` now returns a future - Support null safety - Changed the sql representation of text types from `VARCHAR` to `TEXT` diff --git a/moor/lib/src/runtime/query_builder/statements/select/select.dart b/moor/lib/src/runtime/query_builder/statements/select/select.dart index ea1e6e90..22e48643 100644 --- a/moor/lib/src/runtime/query_builder/statements/select/select.dart +++ b/moor/lib/src/runtime/query_builder/statements/select/select.dart @@ -126,27 +126,51 @@ String _beginOfSelect(bool distinct) { /// multiple entities. class TypedResult { /// Creates the result from the parsed table data. - TypedResult(this._parsedData, this.rawData, [this._parsedExpressions]); + TypedResult(this._parsedData, this.rawData, + [this._parsedExpressions = const {}]); final Map _parsedData; - final Map? _parsedExpressions; + final Map _parsedExpressions; /// The raw data contained in this row. final QueryRow rawData; /// Reads all data that belongs to the given [table] from this row. + /// + /// If this row does not contain non-null columns of the [table], this method + /// will throw an [ArgumentError]. Use [readTableOrNull] for nullable tables. D readTable(TableInfo table) { + if (!_parsedData.containsKey(table)) { + throw ArgumentError( + 'Invalid table passed to readTable: ${table.tableName}. This row ' + 'does not contain values for that table.'); + } + return _parsedData[table] as D; } + /// Reads all data that belongs to the given [table] from this row. + /// + /// Returns `null` if this row does not contain non-null values of the + /// [table]. + /// + /// See also: [readTable], which throws instead of returning `null`. + D? readTableOrNull( + TableInfo table) { + return _parsedData[table] as D?; + } + /// Reads a single column from an [expr]. The expression must have been added /// as a column, for instance via [JoinedSelectStatement.addColumns]. /// /// To access the underlying columns directly, use [rawData]. - D? read(Expression expr) { - if (_parsedExpressions != null) { - return _parsedExpressions![expr] as D; + D read(Expression expr) { + if (_parsedExpressions.containsKey(expr)) { + return _parsedExpressions[expr] as D; } - return null; + + throw ArgumentError( + 'Invalid call to read(): $expr. This result set does not have a column ' + 'for that expression.'); } } diff --git a/moor/lib/src/runtime/query_builder/statements/select/select_with_join.dart b/moor/lib/src/runtime/query_builder/statements/select/select_with_join.dart index 8749f9b3..1874a48c 100644 --- a/moor/lib/src/runtime/query_builder/statements/select/select_with_join.dart +++ b/moor/lib/src/runtime/query_builder/statements/select/select_with_join.dart @@ -212,8 +212,6 @@ class JoinedSelectStatement // if all columns of this table are null, skip the table if (table.$columns.any((c) => row[prefix + c.$name] != null)) { readTables[table] = table.map(row, tablePrefix: table.$tableName); - } else { - readTables[table] = null; } } diff --git a/moor/test/join_test.dart b/moor/test/join_test.dart index 93d13f86..65a633e4 100644 --- a/moor/test/join_test.dart +++ b/moor/test/join_test.dart @@ -1,4 +1,3 @@ -//@dart=2.9 import 'package:mockito/mockito.dart'; import 'package:moor/moor.dart' hide isNull; import 'package:test/test.dart'; @@ -6,8 +5,8 @@ import 'data/tables/todos.dart'; import 'data/utils/mocks.dart'; void main() { - TodoDb db; - MockExecutor executor; + late TodoDb db; + late MockExecutor executor; setUp(() { executor = MockExecutor(); @@ -80,7 +79,7 @@ void main() { verify(executor.runSelect(argThat(contains('DISTINCT')), any)); }); - test('reports null when no data is available', () async { + test('throws when no data is available', () async { when(executor.runSelect(any, any)).thenAnswer((_) { return Future.value([ { @@ -101,7 +100,7 @@ void main() { expect(result, hasLength(1)); final row = result.single; - expect(row.readTable(db.categories), null); + expect(() => row.readTable(db.categories), throwsArgumentError); expect( row.readTable(db.todosTable), TodoEntry( @@ -258,7 +257,7 @@ void main() { 'GROUP BY c.id HAVING COUNT(t.id) >= ?;', [10])); - expect(result.readTable(todos), isNull); + expect(result.readTableOrNull(todos), isNull); expect( result.readTable(categories), Category( @@ -272,20 +271,27 @@ void main() { test('selectWithoutResults', () async { final avgLength = db.todosTable.content.length.avg(); - final query = db.selectOnly(db.todosTable)..addColumns([avgLength]); + final maxLength = db.todosTable.content.length.max(); + final minLength = db.todosTable.content.length.min(); + final query = db.selectOnly(db.todosTable) + ..addColumns([avgLength, maxLength]); when(executor.runSelect(any, any)).thenAnswer((_) async { return [ - {'c0': 3.0} + {'c0': 3.0, 'c1': null}, ]; }); - final result = await query.map((row) => row.read(avgLength)).getSingle(); + final row = await query.getSingle(); verify(executor.runSelect( - 'SELECT AVG(LENGTH(todos.content)) AS "c0" FROM todos;', [])); + 'SELECT AVG(LENGTH(todos.content)) AS "c0", ' + 'MAX(LENGTH(todos.content)) AS "c1" FROM todos;', + [])); - expect(result, 3.0); + expect(row.read(avgLength), 3.0); + expect(row.read(maxLength), isNull); + expect(() => row.read(minLength), throwsArgumentError); }); test('join on JoinedSelectStatement', () async { diff --git a/moor/test/select_test.dart b/moor/test/select_test.dart index 5c7204b4..149c1e9e 100644 --- a/moor/test/select_test.dart +++ b/moor/test/select_test.dart @@ -1,4 +1,3 @@ -//@dart=2.9 import 'dart:async'; import 'package:mockito/mockito.dart'; @@ -23,8 +22,8 @@ final _todoEntry = TodoEntry( ); void main() { - TodoDb db; - MockExecutor executor; + late TodoDb db; + late MockExecutor executor; setUp(() { executor = MockExecutor();