mirror of https://github.com/AMT-Cheif/drift.git
Allow async mappings from SQL to row classes
When existing, custom row classes are used, drift now supports using a (potentially asynchronous) static method to load them instead of just a named constructor like before. Tables are also changed to support the `map` method being async for cases where that is needed. The same applies to custom queries which may have to be async now.
This commit is contained in:
parent
799db04daa
commit
b9a605ed25
|
@ -100,4 +100,4 @@ targets:
|
||||||
global_options:
|
global_options:
|
||||||
":api_index":
|
":api_index":
|
||||||
options:
|
options:
|
||||||
packages: ['drift', 'drift_dev']
|
packages: ['drift', 'drift_dev', 'sqlite3']
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
// #docregion start
|
||||||
|
@UseRowClass(User)
|
||||||
|
class Users extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
DateTimeColumn get birthday => dateTime()();
|
||||||
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final DateTime birthday;
|
||||||
|
|
||||||
|
User({required this.id, required this.name, required this.birthday});
|
||||||
|
}
|
||||||
|
// #enddocregion start
|
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
// #docregion named
|
||||||
|
@UseRowClass(User, constructor: 'fromDb')
|
||||||
|
class Users extends Table {
|
||||||
|
// ...
|
||||||
|
// #enddocregion named
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
DateTimeColumn get birthday => dateTime()();
|
||||||
|
// #docregion named
|
||||||
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final DateTime birthday;
|
||||||
|
|
||||||
|
User.fromDb({required this.id, required this.name, required this.birthday});
|
||||||
|
}
|
||||||
|
// #enddocregion named
|
|
@ -6,6 +6,7 @@ data:
|
||||||
template: layouts/docs/single
|
template: layouts/docs/single
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
For each table declared in Dart or in a drift file, `drift_dev` generates a row class (sometimes also referred to as _data class_)
|
For each table declared in Dart or in a drift file, `drift_dev` generates a row class (sometimes also referred to as _data class_)
|
||||||
to hold a full row and a companion class for updates and inserts.
|
to hold a full row and a companion class for updates and inserts.
|
||||||
This works well for most cases: Drift knows what columns your table has, and it can generate a simple class for all of that.
|
This works well for most cases: Drift knows what columns your table has, and it can generate a simple class for all of that.
|
||||||
|
@ -19,22 +20,8 @@ Starting from moor version 4.3 (and in drift), it is possible to use your own cl
|
||||||
|
|
||||||
To use a custom row class, simply annotate your table definition with `@UseRowClass`.
|
To use a custom row class, simply annotate your table definition with `@UseRowClass`.
|
||||||
|
|
||||||
```dart
|
{% assign snippets = "package:drift_docs/snippets/custom_row_classes/default.dart.excerpt.json" | readString | json_decode %}
|
||||||
@UseRowClass(User)
|
{% include "blocks/snippet" snippets = snippets name = "start" %}
|
||||||
class Users extends Table {
|
|
||||||
IntColumn get id => integer().autoIncrement()();
|
|
||||||
TextColumn get name => text()();
|
|
||||||
DateTimeColumn get birthday => dateTime()();
|
|
||||||
}
|
|
||||||
|
|
||||||
class User {
|
|
||||||
final int id;
|
|
||||||
final String name;
|
|
||||||
final DateTime birthDate;
|
|
||||||
|
|
||||||
User({required this.id, required this.name, required this.birthDate});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A row class must adhere to the following requirements:
|
A row class must adhere to the following requirements:
|
||||||
|
|
||||||
|
@ -58,18 +45,25 @@ By default, drift will use the default, unnamed constructor to map a row to the
|
||||||
If you want to use another constructor, set the `constructor` parameter on the
|
If you want to use another constructor, set the `constructor` parameter on the
|
||||||
`@UseRowClass` annotation:
|
`@UseRowClass` annotation:
|
||||||
|
|
||||||
|
{% assign snippets = "package:drift_docs/snippets/custom_row_classes/named.dart.excerpt.json" | readString | json_decode %}
|
||||||
|
{% include "blocks/snippet" snippets = snippets name = "named" %}
|
||||||
|
|
||||||
|
### Static and aynchronous factories
|
||||||
|
|
||||||
|
Starting with drift 2.0, the custom constructor set with the `constructor`
|
||||||
|
parameter on the `@UseRowClass` annotation may also refer to a static method
|
||||||
|
defined on the class to load.
|
||||||
|
That method must either return the row class or a `Future` of that type.
|
||||||
|
Unlike a named constructor or a factory, this can be useful in case the mapping
|
||||||
|
from SQL to Dart needs to be asynchronous:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
@UseRowClass(User, constructor: 'fromDb')
|
|
||||||
class Users extends Table {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
final int id;
|
// ...
|
||||||
final String name;
|
|
||||||
final DateTime birthDate;
|
|
||||||
|
|
||||||
User.fromDb({required this.id, required this.name, required this.birthDate});
|
static Future<User> load(int id, String name, DateTime birthday) async {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
- __Breaking__: Remove the `includeJoinedTableColumns` parameter on `selectOnly()`.
|
- __Breaking__: Remove the `includeJoinedTableColumns` parameter on `selectOnly()`.
|
||||||
The method now behaves as if that parameter was turned off. To use columns from a
|
The method now behaves as if that parameter was turned off. To use columns from a
|
||||||
joined table, add them with `addColumns`.
|
joined table, add them with `addColumns`.
|
||||||
|
- __Breaking__: Remove the `fromData` factory on generated data classes. Use the
|
||||||
|
`map` method on tables instead.
|
||||||
- Add support for storing date times as (ISO-8601) strings. For details on how
|
- Add support for storing date times as (ISO-8601) strings. For details on how
|
||||||
to use this, see [the documentation](https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#supported-column-types).
|
to use this, see [the documentation](https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#supported-column-types).
|
||||||
- Consistently handle transaction errors like a failing `BEGIN` or `COMMIT`
|
- Consistently handle transaction errors like a failing `BEGIN` or `COMMIT`
|
||||||
|
@ -25,6 +27,9 @@
|
||||||
to delete statatements.
|
to delete statatements.
|
||||||
- Support nested transactions.
|
- Support nested transactions.
|
||||||
- Support custom collations in the query builder API.
|
- Support custom collations in the query builder API.
|
||||||
|
- [Custom row classes](https://drift.simonbinder.eu/docs/advanced-features/custom_row_classes/)
|
||||||
|
can now be constructed with static methods too.
|
||||||
|
These static factories can also be asynchronous.
|
||||||
|
|
||||||
## 1.7.1
|
## 1.7.1
|
||||||
|
|
||||||
|
|
|
@ -324,6 +324,9 @@ extension BuildGeneralColumn<T extends Object> on _BaseColumnBuilder<T> {
|
||||||
/// ```
|
/// ```
|
||||||
/// The generated row class will then use a `MyFancyClass` instead of a
|
/// The generated row class will then use a `MyFancyClass` instead of a
|
||||||
/// `String`, which would usually be used for [Table.text] columns.
|
/// `String`, which would usually be used for [Table.text] columns.
|
||||||
|
///
|
||||||
|
/// The type [T] of the type converter may only be nullable if this column
|
||||||
|
/// was declared [nullable] too. Otherwise, `drift_dev` will emit an error.
|
||||||
ColumnBuilder<T> map<Dart>(TypeConverter<Dart, T?> converter) =>
|
ColumnBuilder<T> map<Dart>(TypeConverter<Dart, T?> converter) =>
|
||||||
_isGenerated();
|
_isGenerated();
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import 'package:drift/src/runtime/executor/stream_queries.dart';
|
||||||
import 'package:drift/src/utils/single_transformer.dart';
|
import 'package:drift/src/utils/single_transformer.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
import '../../utils/async.dart';
|
||||||
|
|
||||||
// New files should not be part of this mega library, which we're trying to
|
// New files should not be part of this mega library, which we're trying to
|
||||||
// split up.
|
// split up.
|
||||||
import 'expressions/case_when.dart';
|
import 'expressions/case_when.dart';
|
||||||
|
|
|
@ -93,7 +93,7 @@ abstract class ResultSetImplementation<Tbl, Row> extends DatabaseSchemaEntity {
|
||||||
List<GeneratedColumn> get $columns;
|
List<GeneratedColumn> get $columns;
|
||||||
|
|
||||||
/// Maps the given row returned by the database into the fitting data class.
|
/// Maps the given row returned by the database into the fitting data class.
|
||||||
Row map(Map<String, dynamic> data, {String? tablePrefix});
|
FutureOr<Row> map(Map<String, dynamic> data, {String? tablePrefix});
|
||||||
|
|
||||||
/// Creates an alias of this table or view that will write the name [alias]
|
/// Creates an alias of this table or view that will write the name [alias]
|
||||||
/// when used in a query.
|
/// when used in a query.
|
||||||
|
@ -125,7 +125,7 @@ class _AliasResultSet<Tbl, Row> extends ResultSetImplementation<Tbl, Row> {
|
||||||
String get entityName => _inner.entityName;
|
String get entityName => _inner.entityName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Row map(Map<String, dynamic> data, {String? tablePrefix}) {
|
FutureOr<Row> map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
return _inner.map(data, tablePrefix: tablePrefix);
|
return _inner.map(data, tablePrefix: tablePrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,8 @@ mixin TableInfo<TableDsl extends Table, D> on Table
|
||||||
/// The [database] instance is used so that the raw values from the companion
|
/// The [database] instance is used so that the raw values from the companion
|
||||||
/// can properly be interpreted as the high-level Dart values exposed by the
|
/// can properly be interpreted as the high-level Dart values exposed by the
|
||||||
/// data class.
|
/// data class.
|
||||||
D mapFromCompanion(Insertable<D> companion, DatabaseConnectionUser database) {
|
Future<D> mapFromCompanion(
|
||||||
|
Insertable<D> companion, DatabaseConnectionUser database) async {
|
||||||
final asColumnMap = companion.toColumns(false);
|
final asColumnMap = companion.toColumns(false);
|
||||||
|
|
||||||
if (asColumnMap.values.any((e) => e is! Variable)) {
|
if (asColumnMap.values.any((e) => e is! Variable)) {
|
||||||
|
@ -122,20 +123,20 @@ mixin VirtualTableInfo<TableDsl extends Table, D> on TableInfo<TableDsl, D> {
|
||||||
/// Most of these are accessed internally by drift or by generated code.
|
/// Most of these are accessed internally by drift or by generated code.
|
||||||
extension TableInfoUtils<TableDsl, D> on ResultSetImplementation<TableDsl, D> {
|
extension TableInfoUtils<TableDsl, D> on ResultSetImplementation<TableDsl, D> {
|
||||||
/// Like [map], but from a [row] instead of the low-level map.
|
/// Like [map], but from a [row] instead of the low-level map.
|
||||||
D mapFromRow(QueryRow row, {String? tablePrefix}) {
|
Future<D> mapFromRow(QueryRow row, {String? tablePrefix}) async {
|
||||||
return map(row.data, tablePrefix: tablePrefix);
|
return map(row.data, tablePrefix: tablePrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [mapFromRow], but returns null if a non-nullable column of this table
|
/// Like [mapFromRow], but returns null if a non-nullable column of this table
|
||||||
/// is null in [row].
|
/// is null in [row].
|
||||||
D? mapFromRowOrNull(QueryRow row, {String? tablePrefix}) {
|
Future<D?> mapFromRowOrNull(QueryRow row, {String? tablePrefix}) {
|
||||||
final resolvedPrefix = tablePrefix == null ? '' : '$tablePrefix.';
|
final resolvedPrefix = tablePrefix == null ? '' : '$tablePrefix.';
|
||||||
|
|
||||||
final notInRow = $columns
|
final notInRow = $columns
|
||||||
.where((c) => !c.$nullable)
|
.where((c) => !c.$nullable)
|
||||||
.any((e) => row.data['$resolvedPrefix${e.$name}'] == null);
|
.any((e) => row.data['$resolvedPrefix${e.$name}'] == null);
|
||||||
|
|
||||||
if (notInRow) return null;
|
if (notInRow) return Future.value(null);
|
||||||
|
|
||||||
return mapFromRow(row, tablePrefix: tablePrefix);
|
return mapFromRow(row, tablePrefix: tablePrefix);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +154,7 @@ extension TableInfoUtils<TableDsl, D> on ResultSetImplementation<TableDsl, D> {
|
||||||
///
|
///
|
||||||
/// Drift would generate code to call this method with `'c1': 'foo'` and
|
/// Drift would generate code to call this method with `'c1': 'foo'` and
|
||||||
/// `'c2': 'bar'` in [alias].
|
/// `'c2': 'bar'` in [alias].
|
||||||
D mapFromRowWithAlias(QueryRow row, Map<String, String> alias) {
|
Future<D> mapFromRowWithAlias(QueryRow row, Map<String, String> alias) async {
|
||||||
return map({
|
return map({
|
||||||
for (final entry in row.data.entries) alias[entry.key]!: entry.value,
|
for (final entry in row.data.entries) alias[entry.key]!: entry.value,
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,7 +78,7 @@ class DeleteStatement<T extends Table, D> extends Query<T, D>
|
||||||
{TableUpdate.onTable(_sourceTable, kind: UpdateKind.delete)});
|
{TableUpdate.onTable(_sourceTable, kind: UpdateKind.delete)});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [for (final rawRow in rows) table.map(rawRow)];
|
return rows.mapAsyncAndAwait(table.map);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ class SimpleSelectStatement<T extends HasResultSet, D> extends Query<T, D>
|
||||||
key: StreamKey(query.sql, query.boundVariables),
|
key: StreamKey(query.sql, query.boundVariables),
|
||||||
);
|
);
|
||||||
|
|
||||||
return database.createStream(fetcher).map(_mapResponse);
|
return database.createStream(fetcher).asyncMap(_mapResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _getRaw(GenerationContext ctx) {
|
Future<List<Map<String, Object?>>> _getRaw(GenerationContext ctx) {
|
||||||
|
@ -69,8 +69,8 @@ class SimpleSelectStatement<T extends HasResultSet, D> extends Query<T, D>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<D> _mapResponse(List<Map<String, Object?>> rows) {
|
Future<List<D>> _mapResponse(List<Map<String, Object?>> rows) {
|
||||||
return rows.map(table.map).toList();
|
return rows.mapAsyncAndAwait(table.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a select statement that operates on more than one table by
|
/// Creates a select statement that operates on more than one table by
|
||||||
|
|
|
@ -207,7 +207,7 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
|
||||||
|
|
||||||
return database
|
return database
|
||||||
.createStream(fetcher)
|
.createStream(fetcher)
|
||||||
.map((rows) => _mapResponse(ctx, rows));
|
.asyncMap((rows) => _mapResponse(ctx, rows));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -234,9 +234,9 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TypedResult> _mapResponse(
|
Future<List<TypedResult>> _mapResponse(
|
||||||
GenerationContext ctx, List<Map<String, Object?>> rows) {
|
GenerationContext ctx, List<Map<String, Object?>> rows) {
|
||||||
return rows.map((row) {
|
return Future.wait(rows.map((row) async {
|
||||||
final readTables = <ResultSetImplementation, dynamic>{};
|
final readTables = <ResultSetImplementation, dynamic>{};
|
||||||
final readColumns = <Expression, dynamic>{};
|
final readColumns = <Expression, dynamic>{};
|
||||||
|
|
||||||
|
@ -244,7 +244,8 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
|
||||||
final prefix = '${table.aliasedName}.';
|
final prefix = '${table.aliasedName}.';
|
||||||
// if all columns of this table are null, skip the table
|
// if all columns of this table are null, skip the table
|
||||||
if (table.$columns.any((c) => row[prefix + c.$name] != null)) {
|
if (table.$columns.any((c) => row[prefix + c.$name] != null)) {
|
||||||
readTables[table] = table.map(row, tablePrefix: table.aliasedName);
|
readTables[table] =
|
||||||
|
await table.map(row, tablePrefix: table.aliasedName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +257,7 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypedResult(readTables, QueryRow(row, database), readColumns);
|
return TypedResult(readTables, QueryRow(row, database), readColumns);
|
||||||
}).toList();
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@alwaysThrows
|
@alwaysThrows
|
||||||
|
|
|
@ -95,7 +95,7 @@ class UpdateStatement<T extends Table, D> extends Query<T, D>
|
||||||
{TableUpdate.onTable(_sourceTable, kind: UpdateKind.update)});
|
{TableUpdate.onTable(_sourceTable, kind: UpdateKind.update)});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [for (final rawRow in rows) table.map(rawRow)];
|
return rows.mapAsyncAndAwait(table.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces the old version of [entity] that is stored in the database with
|
/// Replaces the old version of [entity] that is stored in the database with
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
@internal
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// Drift-internal utilities to map potentially async operations.
|
||||||
|
extension MapAndAwait<T> on Iterable<T> {
|
||||||
|
/// A variant of [Future.wait] that also works for [FutureOr].
|
||||||
|
Future<List<R>> mapAsyncAndAwait<R>(FutureOr<R> Function(T) mapper) {
|
||||||
|
return Future.wait(map((e) => Future.sync(() => mapper(e))));
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,20 +34,20 @@ void main() {
|
||||||
expect(aliasA.hashCode == db.alias(db.users, 'a').hashCode, isTrue);
|
expect(aliasA.hashCode == db.alias(db.users, 'a').hashCode, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can convert a companion to a row class', () {
|
test('can convert a companion to a row class', () async {
|
||||||
const companion = SharedTodosCompanion(
|
const companion = SharedTodosCompanion(
|
||||||
todo: Value(3),
|
todo: Value(3),
|
||||||
user: Value(4),
|
user: Value(4),
|
||||||
);
|
);
|
||||||
|
|
||||||
final user = db.sharedTodos.mapFromCompanion(companion, db);
|
final user = await db.sharedTodos.mapFromCompanion(companion, db);
|
||||||
expect(
|
expect(
|
||||||
user,
|
user,
|
||||||
const SharedTodo(todo: 3, user: 4),
|
const SharedTodo(todo: 3, user: 4),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can map from row without table prefix', () {
|
test('can map from row without table prefix', () async {
|
||||||
final rowData = {
|
final rowData = {
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'title': 'some title',
|
'title': 'some title',
|
||||||
|
@ -55,7 +55,7 @@ void main() {
|
||||||
'target_date': null,
|
'target_date': null,
|
||||||
'category': null,
|
'category': null,
|
||||||
};
|
};
|
||||||
final todo = db.todosTable.mapFromRowOrNull(QueryRow(rowData, db));
|
final todo = await db.todosTable.mapFromRowOrNull(QueryRow(rowData, db));
|
||||||
expect(
|
expect(
|
||||||
todo,
|
todo,
|
||||||
const TodoEntry(
|
const TodoEntry(
|
||||||
|
|
|
@ -1596,7 +1596,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
config,
|
config,
|
||||||
}).map((QueryRow row) => config.mapFromRowWithAlias(row, const {
|
}).asyncMap((QueryRow row) => config.mapFromRowWithAlias(row, const {
|
||||||
'ck': 'config_key',
|
'ck': 'config_key',
|
||||||
'cf': 'config_value',
|
'cf': 'config_value',
|
||||||
'cs1': 'sync_state',
|
'cs1': 'sync_state',
|
||||||
|
@ -1621,7 +1621,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
config,
|
config,
|
||||||
...generatedclause.watchedTables,
|
...generatedclause.watchedTables,
|
||||||
}).map(config.mapFromRow);
|
}).asyncMap(config.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<Config> readDynamic(
|
Selectable<Config> readDynamic(
|
||||||
|
@ -1638,7 +1638,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
config,
|
config,
|
||||||
...generatedpredicate.watchedTables,
|
...generatedpredicate.watchedTables,
|
||||||
}).map(config.mapFromRow);
|
}).asyncMap(config.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<String> typeConverterVar(SyncType? var1, List<SyncType?> var2,
|
Selectable<String> typeConverterVar(SyncType? var1, List<SyncType?> var2,
|
||||||
|
@ -1710,12 +1710,12 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
withDefaults,
|
withDefaults,
|
||||||
withConstraints,
|
withConstraints,
|
||||||
...generatedpredicate.watchedTables,
|
...generatedpredicate.watchedTables,
|
||||||
}).map((QueryRow row) {
|
}).asyncMap((QueryRow row) async {
|
||||||
return MultipleResult(
|
return MultipleResult(
|
||||||
row: row,
|
row: row,
|
||||||
a: row.readNullable<String>('a'),
|
a: row.readNullable<String>('a'),
|
||||||
b: row.readNullable<int>('b'),
|
b: row.readNullable<int>('b'),
|
||||||
c: withConstraints.mapFromRowOrNull(row, tablePrefix: 'nested_0'),
|
c: await withConstraints.mapFromRowOrNull(row, tablePrefix: 'nested_0'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1728,7 +1728,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
email,
|
email,
|
||||||
}).map(email.mapFromRow);
|
}).asyncMap(email.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<ReadRowIdResult> readRowId(
|
Selectable<ReadRowIdResult> readRowId(
|
||||||
|
@ -1762,7 +1762,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
Selectable<MyViewData> readView() {
|
Selectable<MyViewData> readView() {
|
||||||
return customSelect('SELECT * FROM my_view', variables: [], readsFrom: {
|
return customSelect('SELECT * FROM my_view', variables: [], readsFrom: {
|
||||||
config,
|
config,
|
||||||
}).map(myView.mapFromRow);
|
}).asyncMap(myView.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<int> cfeTest() {
|
Selectable<int> cfeTest() {
|
||||||
|
@ -1786,9 +1786,10 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
$writeInsertable(this.config, value, startIndex: $arrayStartIndex);
|
$writeInsertable(this.config, value, startIndex: $arrayStartIndex);
|
||||||
$arrayStartIndex += generatedvalue.amountOfVariables;
|
$arrayStartIndex += generatedvalue.amountOfVariables;
|
||||||
return customWriteReturning(
|
return customWriteReturning(
|
||||||
'INSERT INTO config ${generatedvalue.sql} RETURNING *',
|
'INSERT INTO config ${generatedvalue.sql} RETURNING *',
|
||||||
variables: [...generatedvalue.introducedVariables],
|
variables: [...generatedvalue.introducedVariables],
|
||||||
updates: {config}).then((rows) => rows.map(config.mapFromRow).toList());
|
updates: {config})
|
||||||
|
.then((rows) => Future.wait(rows.map(config.mapFromRow)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<NestedResult> nested(String? var1) {
|
Selectable<NestedResult> nested(String? var1) {
|
||||||
|
@ -1803,7 +1804,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
}).asyncMap((QueryRow row) async {
|
}).asyncMap((QueryRow row) async {
|
||||||
return NestedResult(
|
return NestedResult(
|
||||||
row: row,
|
row: row,
|
||||||
defaults: withDefaults.mapFromRow(row, tablePrefix: 'nested_0'),
|
defaults: await withDefaults.mapFromRow(row, tablePrefix: 'nested_0'),
|
||||||
nestedQuery0: await customSelect(
|
nestedQuery0: await customSelect(
|
||||||
'SELECT * FROM with_constraints AS c WHERE c.b = ?1',
|
'SELECT * FROM with_constraints AS c WHERE c.b = ?1',
|
||||||
variables: [
|
variables: [
|
||||||
|
@ -1812,7 +1813,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
withConstraints,
|
withConstraints,
|
||||||
withDefaults,
|
withDefaults,
|
||||||
}).map(withConstraints.mapFromRow).get(),
|
}).asyncMap(withConstraints.mapFromRow).get(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1642,7 +1642,7 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
todosTable,
|
todosTable,
|
||||||
}).map(todosTable.mapFromRow);
|
}).asyncMap(todosTable.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<TodoEntry> search({required int id}) {
|
Selectable<TodoEntry> search({required int id}) {
|
||||||
|
@ -1653,7 +1653,7 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
todosTable,
|
todosTable,
|
||||||
}).map(todosTable.mapFromRow);
|
}).asyncMap(todosTable.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<MyCustomObject> findCustom() {
|
Selectable<MyCustomObject> findCustom() {
|
||||||
|
@ -1750,6 +1750,6 @@ mixin _$SomeDaoMixin on DatabaseAccessor<TodoDb> {
|
||||||
todosTable,
|
todosTable,
|
||||||
sharedTodos,
|
sharedTodos,
|
||||||
users,
|
users,
|
||||||
}).map(todosTable.mapFromRow);
|
}).asyncMap(todosTable.mapFromRow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,5 +53,7 @@ class CustomTable extends Table with TableInfo<CustomTable, Null> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Null map(Map<String, dynamic> data, {String? tablePrefix}) => null;
|
Future<Null> map(Map<String, dynamic> data, {String? tablePrefix}) async {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,13 @@ ExistingRowClass? validateExistingClass(
|
||||||
Step step) {
|
Step step) {
|
||||||
final errors = step.errors;
|
final errors = step.errors;
|
||||||
final desiredClass = dartClass.classElement;
|
final desiredClass = dartClass.classElement;
|
||||||
ConstructorElement? ctor;
|
final library = desiredClass.library;
|
||||||
|
|
||||||
|
ExecutableElement? ctor;
|
||||||
|
final InterfaceType instantiation;
|
||||||
|
|
||||||
if (dartClass.instantiation != null) {
|
if (dartClass.instantiation != null) {
|
||||||
final instantiation = desiredClass.instantiate(
|
instantiation = desiredClass.instantiate(
|
||||||
typeArguments: dartClass.instantiation!,
|
typeArguments: dartClass.instantiation!,
|
||||||
nullabilitySuffix: NullabilitySuffix.none,
|
nullabilitySuffix: NullabilitySuffix.none,
|
||||||
);
|
);
|
||||||
|
@ -41,6 +44,36 @@ ExistingRowClass? validateExistingClass(
|
||||||
ctor = instantiation.lookUpConstructor(constructor, desiredClass.library);
|
ctor = instantiation.lookUpConstructor(constructor, desiredClass.library);
|
||||||
} else {
|
} else {
|
||||||
ctor = desiredClass.getNamedConstructor(constructor);
|
ctor = desiredClass.getNamedConstructor(constructor);
|
||||||
|
instantiation = library.typeSystem.instantiateInterfaceToBounds(
|
||||||
|
element: desiredClass, nullabilitySuffix: NullabilitySuffix.none);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctor == null) {
|
||||||
|
final fallback = desiredClass.getMethod(constructor);
|
||||||
|
|
||||||
|
if (fallback != null) {
|
||||||
|
if (!fallback.isStatic) {
|
||||||
|
errors.report(ErrorInDartCode(
|
||||||
|
affectedElement: fallback,
|
||||||
|
message: 'To use this method as a factory for the custom row class, '
|
||||||
|
'it needs to be static.',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The static factory must return a subtype of `FutureOr<ThatRowClass>`
|
||||||
|
final expectedReturnType =
|
||||||
|
library.typeProvider.futureOrType(instantiation);
|
||||||
|
if (!library.typeSystem
|
||||||
|
.isAssignableTo(fallback.returnType, expectedReturnType)) {
|
||||||
|
errors.report(ErrorInDartCode(
|
||||||
|
affectedElement: fallback,
|
||||||
|
message: 'To be used as a factory for the custom row class, this '
|
||||||
|
'method needs to return an instance of it.',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctor = fallback;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctor == null) {
|
if (ctor == null) {
|
||||||
|
|
|
@ -69,15 +69,31 @@ class ExistingRowClass {
|
||||||
|
|
||||||
/// The Dart types that should be used to instantiate the [targetClass].
|
/// The Dart types that should be used to instantiate the [targetClass].
|
||||||
final List<DartType> typeInstantiation;
|
final List<DartType> typeInstantiation;
|
||||||
final ConstructorElement constructor;
|
|
||||||
|
/// The method to use when instantiating the row class.
|
||||||
|
///
|
||||||
|
/// This may either be a constructor or a static method on the row class.
|
||||||
|
final ExecutableElement constructor;
|
||||||
|
|
||||||
final Map<DriftColumn, ParameterElement> mapping;
|
final Map<DriftColumn, ParameterElement> mapping;
|
||||||
|
|
||||||
/// Generate toCompanion for data class
|
/// Generate toCompanion for data class
|
||||||
final bool generateInsertable;
|
final bool generateInsertable;
|
||||||
|
|
||||||
ExistingRowClass(
|
ExistingRowClass(
|
||||||
this.targetClass, this.constructor, this.mapping, this.generateInsertable,
|
this.targetClass,
|
||||||
{this.typeInstantiation = const []});
|
this.constructor,
|
||||||
|
this.mapping,
|
||||||
|
this.generateInsertable, {
|
||||||
|
this.typeInstantiation = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Whether the [constructor] returns a future and thus needs to be awaited
|
||||||
|
/// to create an instance of the custom row class.
|
||||||
|
bool get isAsyncFactory {
|
||||||
|
final typeSystem = targetClass.library.typeSystem;
|
||||||
|
return typeSystem.flatten(constructor.returnType) != constructor.returnType;
|
||||||
|
}
|
||||||
|
|
||||||
String dartType([GenerationOptions options = const GenerationOptions()]) {
|
String dartType([GenerationOptions options = const GenerationOptions()]) {
|
||||||
if (typeInstantiation.isEmpty) {
|
if (typeInstantiation.isEmpty) {
|
||||||
|
|
|
@ -118,6 +118,17 @@ abstract class SqlQuery {
|
||||||
placeholders = elements.whereType<FoundDartPlaceholder>().toList();
|
placeholders = elements.whereType<FoundDartPlaceholder>().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get needsAsyncMapping {
|
||||||
|
final result = resultSet;
|
||||||
|
if (result != null) {
|
||||||
|
// Mapping to tables is asynchronous
|
||||||
|
if (result.matchingTable != null) return true;
|
||||||
|
if (result.nestedResults.any((e) => e is NestedResultTable)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
String get resultClassName {
|
String get resultClassName {
|
||||||
final resultSet = this.resultSet;
|
final resultSet = this.resultSet;
|
||||||
if (resultSet == null) {
|
if (resultSet == null) {
|
||||||
|
@ -194,6 +205,9 @@ class SqlSelectQuery extends SqlQuery {
|
||||||
bool get hasNestedQuery =>
|
bool get hasNestedQuery =>
|
||||||
resultSet.nestedResults.any((e) => e is NestedResultQuery);
|
resultSet.nestedResults.any((e) => e is NestedResultQuery);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get needsAsyncMapping => hasNestedQuery || super.needsAsyncMapping;
|
||||||
|
|
||||||
SqlSelectQuery(
|
SqlSelectQuery(
|
||||||
String name,
|
String name,
|
||||||
this.fromContext,
|
this.fromContext,
|
||||||
|
|
|
@ -115,7 +115,7 @@ class QueryWriter {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_buffer.write('(QueryRow row) ');
|
_buffer.write('(QueryRow row) ');
|
||||||
if (query is SqlSelectQuery && query.hasNestedQuery) {
|
if (query.needsAsyncMapping) {
|
||||||
_buffer.write('async ');
|
_buffer.write('async ');
|
||||||
}
|
}
|
||||||
_buffer.write('{ return ${query.resultClassName}(');
|
_buffer.write('{ return ${query.resultClassName}(');
|
||||||
|
@ -140,7 +140,7 @@ class QueryWriter {
|
||||||
final mappingMethod =
|
final mappingMethod =
|
||||||
nested.isNullable ? 'mapFromRowOrNull' : 'mapFromRow';
|
nested.isNullable ? 'mapFromRowOrNull' : 'mapFromRow';
|
||||||
|
|
||||||
_buffer.write('$fieldName: $tableGetter.$mappingMethod(row, '
|
_buffer.write('$fieldName: await $tableGetter.$mappingMethod(row, '
|
||||||
'tablePrefix: ${asDartLiteral(prefix)}),');
|
'tablePrefix: ${asDartLiteral(prefix)}),');
|
||||||
} else if (nested is NestedResultQuery) {
|
} else if (nested is NestedResultQuery) {
|
||||||
final fieldName = nested.filedName();
|
final fieldName = nested.filedName();
|
||||||
|
@ -173,8 +173,8 @@ class QueryWriter {
|
||||||
// The type converter maps non-nullable types, but the column may be
|
// The type converter maps non-nullable types, but the column may be
|
||||||
// nullable in SQL => just map null to null and only invoke the type
|
// nullable in SQL => just map null to null and only invoke the type
|
||||||
// converter for non-null values.
|
// converter for non-null values.
|
||||||
code = 'NullAwareTypeConverter.wrapFromSql(${_converter(converter)}, '
|
code = 'NullAwareTypeConverter.wrapFromSql'
|
||||||
'$code)';
|
'(${_converter(converter)}, $code)';
|
||||||
} else {
|
} else {
|
||||||
// Just apply the type converter directly.
|
// Just apply the type converter directly.
|
||||||
code = '${_converter(converter)}.fromSql($code)';
|
code = '${_converter(converter)}.fromSql($code)';
|
||||||
|
@ -206,7 +206,7 @@ class QueryWriter {
|
||||||
_buffer.write(', ');
|
_buffer.write(', ');
|
||||||
_writeReadsFrom(select);
|
_writeReadsFrom(select);
|
||||||
|
|
||||||
if (select.hasNestedQuery) {
|
if (select.needsAsyncMapping) {
|
||||||
_buffer.write(').asyncMap(');
|
_buffer.write(').asyncMap(');
|
||||||
} else {
|
} else {
|
||||||
_buffer.write(').map(');
|
_buffer.write(').map(');
|
||||||
|
@ -259,9 +259,18 @@ class QueryWriter {
|
||||||
_writeExpandedDeclarations(update);
|
_writeExpandedDeclarations(update);
|
||||||
_buffer.write('return customWriteReturning(${_queryCode(update)},');
|
_buffer.write('return customWriteReturning(${_queryCode(update)},');
|
||||||
_writeCommonUpdateParameters(update);
|
_writeCommonUpdateParameters(update);
|
||||||
_buffer.write(').then((rows) => rows.map(');
|
|
||||||
_writeMappingLambda(update);
|
_buffer.write(').then((rows) => ');
|
||||||
_buffer.write(').toList());\n}');
|
if (update.needsAsyncMapping) {
|
||||||
|
_buffer.write('Future.wait(rows.map(');
|
||||||
|
_writeMappingLambda(update);
|
||||||
|
_buffer.write('))');
|
||||||
|
} else {
|
||||||
|
_buffer.write('rows.map(');
|
||||||
|
_writeMappingLambda(update);
|
||||||
|
_buffer.write(')');
|
||||||
|
}
|
||||||
|
_buffer.write(');\n}');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeUpdatingQuery(UpdatingQuery update) {
|
void _writeUpdatingQuery(UpdatingQuery update) {
|
||||||
|
|
|
@ -136,9 +136,13 @@ abstract class TableOrViewWriter {
|
||||||
|
|
||||||
final dataClassName = tableOrView.dartTypeCode();
|
final dataClassName = tableOrView.dartTypeCode();
|
||||||
|
|
||||||
|
final isAsync = tableOrView.existingRowClass?.isAsyncFactory == true;
|
||||||
|
final returnType = isAsync ? 'Future<$dataClassName>' : dataClassName;
|
||||||
|
final asyncModifier = isAsync ? 'async' : '';
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
..write('@override\n$dataClassName map(Map<String, dynamic> data, '
|
..write('@override $returnType map(Map<String, dynamic> data, '
|
||||||
'{String? tablePrefix}) {\n')
|
'{String? tablePrefix}) $asyncModifier {\n')
|
||||||
..write('final effectivePrefix = '
|
..write('final effectivePrefix = '
|
||||||
"tablePrefix != null ? '\$tablePrefix.' : '';");
|
"tablePrefix != null ? '\$tablePrefix.' : '';");
|
||||||
|
|
||||||
|
@ -174,6 +178,7 @@ abstract class TableOrViewWriter {
|
||||||
final ctor = info.constructor;
|
final ctor = info.constructor;
|
||||||
buffer
|
buffer
|
||||||
..write('return ')
|
..write('return ')
|
||||||
|
..write(isAsync ? 'await ' : '')
|
||||||
..write(classElement.name);
|
..write(classElement.name);
|
||||||
if (ctor.name.isNotEmpty) {
|
if (ctor.name.isNotEmpty) {
|
||||||
buffer
|
buffer
|
||||||
|
|
|
@ -148,10 +148,50 @@ class Cls extends HasBar {
|
||||||
|
|
||||||
Cls(this.foo, int bar): super(bar);
|
Cls(this.foo, int bar): super(bar);
|
||||||
}
|
}
|
||||||
|
''',
|
||||||
|
'a|lib/async_factory.dart': '''
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
@UseRowClass(MyCustomClass, constructor: 'load')
|
||||||
|
class Tbl extends Table {
|
||||||
|
TextColumn get foo => text()();
|
||||||
|
IntColumn get bar => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyCustomClass {
|
||||||
|
static Future<MyCustomClass> load(String foo, int bar) async {
|
||||||
|
throw 'stub';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
'a|lib/invalid_static_factory.dart': '''
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
@UseRowClass(MyCustomClass, constructor: 'invalidReturn')
|
||||||
|
class Tbl extends Table {
|
||||||
|
TextColumn get foo => text()();
|
||||||
|
IntColumn get bar => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseRowClass(MyCustomClass, constructor: 'notStatic')
|
||||||
|
class Tbl2 extends Table {
|
||||||
|
TextColumn get foo => text()();
|
||||||
|
IntColumn get bar => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyCustomClass {
|
||||||
|
static String invalidReturn(String foo, int bar) async {
|
||||||
|
throw 'stub';
|
||||||
|
}
|
||||||
|
|
||||||
|
MyRowClass notStatic() {
|
||||||
|
throw 'stub';
|
||||||
|
}
|
||||||
|
}
|
||||||
''',
|
''',
|
||||||
'a|lib/custom_parent_class_no_error.dart': '''
|
'a|lib/custom_parent_class_no_error.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
abstract class BaseModel extends DataClass {
|
abstract class BaseModel extends DataClass {
|
||||||
abstract final String id;
|
abstract final String id;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +204,7 @@ class Companies extends Table {
|
||||||
''',
|
''',
|
||||||
'a|lib/custom_parent_class_typed_no_error.dart': '''
|
'a|lib/custom_parent_class_typed_no_error.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
abstract class BaseModel<T> extends DataClass {
|
abstract class BaseModel<T> extends DataClass {
|
||||||
abstract final String id;
|
abstract final String id;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +217,7 @@ class Companies extends Table {
|
||||||
''',
|
''',
|
||||||
'a|lib/custom_parent_class_no_super.dart': '''
|
'a|lib/custom_parent_class_no_super.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
abstract class BaseModel {
|
abstract class BaseModel {
|
||||||
abstract final String id;
|
abstract final String id;
|
||||||
}
|
}
|
||||||
|
@ -190,7 +230,7 @@ class Companies extends Table {
|
||||||
''',
|
''',
|
||||||
'a|lib/custom_parent_class_wrong_super.dart': '''
|
'a|lib/custom_parent_class_wrong_super.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
class Test {
|
class Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +246,7 @@ class Companies extends Table {
|
||||||
''',
|
''',
|
||||||
'a|lib/custom_parent_class_typed_wrong_type_arg.dart': '''
|
'a|lib/custom_parent_class_typed_wrong_type_arg.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
abstract class BaseModel<T> extends DataClass {
|
abstract class BaseModel<T> extends DataClass {
|
||||||
abstract final String id;
|
abstract final String id;
|
||||||
}
|
}
|
||||||
|
@ -219,7 +259,7 @@ class Companies extends Table {
|
||||||
''',
|
''',
|
||||||
'a|lib/custom_parent_class_two_type_argument.dart': '''
|
'a|lib/custom_parent_class_two_type_argument.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
abstract class BaseModel<T, D> extends DataClass {
|
abstract class BaseModel<T, D> extends DataClass {
|
||||||
abstract final String id;
|
abstract final String id;
|
||||||
}
|
}
|
||||||
|
@ -234,7 +274,7 @@ class Companies extends Table {
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
typedef NotClass = void Function();
|
typedef NotClass = void Function();
|
||||||
|
|
||||||
@DataClassName('Company', extending: NotClass)
|
@DataClassName('Company', extending: NotClass)
|
||||||
class Companies extends Table {
|
class Companies extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
|
@ -306,6 +346,35 @@ class Companies extends Table {
|
||||||
contains('but some are missing: bar'))),
|
contains('but some are missing: bar'))),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('for invalid static factories', () async {
|
||||||
|
final file = await state.analyze('package:a/invalid_static_factory.dart');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
file.errors.errors,
|
||||||
|
allOf(
|
||||||
|
contains(
|
||||||
|
isA<ErrorInDartCode>()
|
||||||
|
.having(
|
||||||
|
(e) => e.message,
|
||||||
|
'message',
|
||||||
|
contains('it needs to be static'),
|
||||||
|
)
|
||||||
|
.having((e) => e.affectedElement?.name,
|
||||||
|
'affectedElement.name', 'notStatic'),
|
||||||
|
),
|
||||||
|
contains(
|
||||||
|
isA<ErrorInDartCode>()
|
||||||
|
.having(
|
||||||
|
(e) => e.message,
|
||||||
|
'message',
|
||||||
|
contains('needs to return an instance of it'),
|
||||||
|
)
|
||||||
|
.having((e) => e.affectedElement?.name,
|
||||||
|
'affectedElement.name', 'invalidReturn'),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('supports generic row classes', () async {
|
test('supports generic row classes', () async {
|
||||||
|
@ -361,6 +430,17 @@ class Companies extends Table {
|
||||||
expect(file.errors.errors, isEmpty);
|
expect(file.errors.errors, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('supports async factories for existing row classes', () async {
|
||||||
|
final file = await state.analyze('package:a/async_factory.dart');
|
||||||
|
expect(file.errors.errors, isEmpty);
|
||||||
|
|
||||||
|
final table = file.currentResult!.declaredTables.single;
|
||||||
|
expect(
|
||||||
|
table.existingRowClass,
|
||||||
|
isA<ExistingRowClass>()
|
||||||
|
.having((e) => e.isAsyncFactory, 'isAsyncFactory', isTrue));
|
||||||
|
});
|
||||||
|
|
||||||
group('custom data class parent', () {
|
group('custom data class parent', () {
|
||||||
test('check valid', () async {
|
test('check valid', () async {
|
||||||
final file =
|
final file =
|
||||||
|
|
|
@ -9,10 +9,17 @@ import 'package:drift_dev/src/backends/build/drift_builder.dart';
|
||||||
import 'package:pub_semver/pub_semver.dart';
|
import 'package:pub_semver/pub_semver.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
const _testInput = r'''
|
void main() {
|
||||||
|
test(
|
||||||
|
'generates const constructor for data classes can companion classes',
|
||||||
|
() async {
|
||||||
|
await testBuilder(
|
||||||
|
DriftPartBuilder(const BuilderOptions({}), isForNewDriftPackage: true),
|
||||||
|
const {
|
||||||
|
'a|lib/main.dart': r'''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
part 'main.moor.dart';
|
part 'main.drift.dart';
|
||||||
|
|
||||||
class Users extends Table {
|
class Users extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
@ -23,18 +30,11 @@ class Users extends Table {
|
||||||
tables: [Users],
|
tables: [Users],
|
||||||
)
|
)
|
||||||
class Database extends _$Database {}
|
class Database extends _$Database {}
|
||||||
''';
|
'''
|
||||||
|
},
|
||||||
void main() {
|
|
||||||
test(
|
|
||||||
'generates const constructor for data classes can companion classes',
|
|
||||||
() async {
|
|
||||||
await testBuilder(
|
|
||||||
DriftPartBuilder(const BuilderOptions({})),
|
|
||||||
const {'a|lib/main.dart': _testInput},
|
|
||||||
reader: await PackageAssetReader.currentIsolate(),
|
reader: await PackageAssetReader.currentIsolate(),
|
||||||
outputs: const {
|
outputs: const {
|
||||||
'a|lib/main.moor.dart': _GeneratesConstDataClasses(
|
'a|lib/main.drift.dart': _GeneratesConstDataClasses(
|
||||||
{'User', 'UsersCompanion'},
|
{'User', 'UsersCompanion'},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -42,6 +42,56 @@ void main() {
|
||||||
},
|
},
|
||||||
tags: 'analyzer',
|
tags: 'analyzer',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'generates async mapping code for existing row class with async factory',
|
||||||
|
() async {
|
||||||
|
await testBuilder(
|
||||||
|
DriftPartBuilder(const BuilderOptions({}), isForNewDriftPackage: true),
|
||||||
|
const {
|
||||||
|
'a|lib/main.dart': r'''
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
part 'main.drift.dart';
|
||||||
|
|
||||||
|
@UseRowClass(MyCustomClass, constructor: 'load')
|
||||||
|
class Tbl extends Table {
|
||||||
|
TextColumn get foo => text()();
|
||||||
|
IntColumn get bar => integer()();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyCustomClass {
|
||||||
|
static Future<MyCustomClass> load(String foo, int bar) async {
|
||||||
|
throw 'stub';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DriftDatabase(
|
||||||
|
tables: [Tbl],
|
||||||
|
)
|
||||||
|
class Database extends _$Database {}
|
||||||
|
'''
|
||||||
|
},
|
||||||
|
reader: await PackageAssetReader.currentIsolate(),
|
||||||
|
outputs: {
|
||||||
|
'a|lib/main.drift.dart': decodedMatches(contains(r'''
|
||||||
|
@override
|
||||||
|
Future<MyCustomClass> map(Map<String, dynamic> data,
|
||||||
|
{String? tablePrefix}) async {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return await MyCustomClass.load(
|
||||||
|
attachedDatabase.options.types
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}foo'])!,
|
||||||
|
attachedDatabase.options.types
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}bar'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
''')),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
tags: 'analyzer',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GeneratesConstDataClasses extends Matcher {
|
class _GeneratesConstDataClasses extends Matcher {
|
||||||
|
|
|
@ -651,10 +651,10 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||||
textEntries,
|
textEntries,
|
||||||
todoEntries,
|
todoEntries,
|
||||||
categories,
|
categories,
|
||||||
}).map((QueryRow row) {
|
}).asyncMap((QueryRow row) async {
|
||||||
return SearchResult(
|
return SearchResult(
|
||||||
todos: todoEntries.mapFromRow(row, tablePrefix: 'nested_0'),
|
todos: await todoEntries.mapFromRow(row, tablePrefix: 'nested_0'),
|
||||||
cat: categories.mapFromRowOrNull(row, tablePrefix: 'nested_1'),
|
cat: await categories.mapFromRowOrNull(row, tablePrefix: 'nested_1'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,7 @@ abstract class _$MyDatabase extends GeneratedDatabase {
|
||||||
Selectable<Entrie> allEntries() {
|
Selectable<Entrie> allEntries() {
|
||||||
return customSelect('SELECT * FROM entries', variables: [], readsFrom: {
|
return customSelect('SELECT * FROM entries', variables: [], readsFrom: {
|
||||||
entries,
|
entries,
|
||||||
}).map(entries.mapFromRow);
|
}).asyncMap(entries.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> addEntry(String var1) {
|
Future<int> addEntry(String var1) {
|
||||||
|
|
|
@ -183,7 +183,7 @@ abstract class _$MyDatabase extends GeneratedDatabase {
|
||||||
Selectable<Entrie> allEntries() {
|
Selectable<Entrie> allEntries() {
|
||||||
return customSelect('SELECT * FROM entries', variables: [], readsFrom: {
|
return customSelect('SELECT * FROM entries', variables: [], readsFrom: {
|
||||||
entries,
|
entries,
|
||||||
}).map(entries.mapFromRow);
|
}).asyncMap(entries.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> addEntry(String var1) {
|
Future<int> addEntry(String var1) {
|
||||||
|
|
|
@ -296,7 +296,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
||||||
@override
|
@override
|
||||||
Set<GeneratedColumn> get $primaryKey => {id};
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
@override
|
@override
|
||||||
User map(Map<String, dynamic> data, {String? tablePrefix}) {
|
Future<User> map(Map<String, dynamic> data, {String? tablePrefix}) async {
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return User(
|
return User(
|
||||||
id: attachedDatabase.options.types
|
id: attachedDatabase.options.types
|
||||||
|
@ -526,7 +526,8 @@ class $FriendshipsTable extends Friendships
|
||||||
@override
|
@override
|
||||||
Set<GeneratedColumn> get $primaryKey => {firstUser, secondUser};
|
Set<GeneratedColumn> get $primaryKey => {firstUser, secondUser};
|
||||||
@override
|
@override
|
||||||
Friendship map(Map<String, dynamic> data, {String? tablePrefix}) {
|
Future<Friendship> map(Map<String, dynamic> data,
|
||||||
|
{String? tablePrefix}) async {
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return Friendship(
|
return Friendship(
|
||||||
firstUser: attachedDatabase.options.types
|
firstUser: attachedDatabase.options.types
|
||||||
|
@ -558,7 +559,7 @@ abstract class _$Database extends GeneratedDatabase {
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
users,
|
users,
|
||||||
friendships,
|
friendships,
|
||||||
}).map(users.mapFromRow);
|
}).asyncMap(users.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<int> amountOfGoodFriends(int user) {
|
Selectable<int> amountOfGoodFriends(int user) {
|
||||||
|
@ -581,10 +582,10 @@ abstract class _$Database extends GeneratedDatabase {
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
friendships,
|
friendships,
|
||||||
users,
|
users,
|
||||||
}).map((QueryRow row) {
|
}).asyncMap((QueryRow row) async {
|
||||||
return FriendshipsOfResult(
|
return FriendshipsOfResult(
|
||||||
reallyGoodFriends: row.read<bool>('really_good_friends'),
|
reallyGoodFriends: row.read<bool>('really_good_friends'),
|
||||||
user: users.mapFromRow(row, tablePrefix: 'nested_0'),
|
user: await users.mapFromRow(row, tablePrefix: 'nested_0'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -618,7 +619,7 @@ abstract class _$Database extends GeneratedDatabase {
|
||||||
],
|
],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
users,
|
users,
|
||||||
}).map(users.mapFromRow);
|
}).asyncMap(users.mapFromRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Friendship>> returning(int var1, int var2, bool var3) {
|
Future<List<Friendship>> returning(int var1, int var2, bool var3) {
|
||||||
|
@ -631,7 +632,7 @@ abstract class _$Database extends GeneratedDatabase {
|
||||||
],
|
],
|
||||||
updates: {
|
updates: {
|
||||||
friendships
|
friendships
|
||||||
}).then((rows) => rows.map(friendships.mapFromRow).toList());
|
}).then((rows) => Future.wait(rows.map(friendships.mapFromRow)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
Loading…
Reference in New Issue