mirror of https://github.com/AMT-Cheif/drift.git
API and parser for custom types
This commit is contained in:
parent
dcc7f29492
commit
c2bff3ae42
|
@ -1,3 +1,6 @@
|
||||||
|
## unreleased
|
||||||
|
- Support custom columns
|
||||||
|
|
||||||
## 1.6.0
|
## 1.6.0
|
||||||
- Experimental web support! See [the documentation](https://moor.simonbinder.eu/web) for details.
|
- Experimental web support! See [the documentation](https://moor.simonbinder.eu/web) for details.
|
||||||
- Make transactions easier to use: Thanks to some Dart async magic, you no longer need to run
|
- Make transactions easier to use: Thanks to some Dart async magic, you no longer need to run
|
||||||
|
|
|
@ -107,6 +107,10 @@ class ColumnBuilder<
|
||||||
/// store the current date/time as a default value.
|
/// store the current date/time as a default value.
|
||||||
Builder withDefault(Expression<ResultDartType, ResultSqlType> e) => null;
|
Builder withDefault(Expression<ResultDartType, ResultSqlType> e) => null;
|
||||||
|
|
||||||
|
/// Uses a custom [converter] to store custom Dart objects in a single column
|
||||||
|
/// and automatically mapping them from and to sql.
|
||||||
|
Builder map<T>(TypeConverter<T, ResultDartType> converter) => null;
|
||||||
|
|
||||||
/// Turns this column builder into a column. This method won't actually be
|
/// Turns this column builder into a column. This method won't actually be
|
||||||
/// called in your code. Instead, moor_generator will take a look at your
|
/// called in your code. Instead, moor_generator will take a look at your
|
||||||
/// source code to figure out your table structure.
|
/// source code to figure out your table structure.
|
||||||
|
|
|
@ -93,7 +93,6 @@ abstract class Table {
|
||||||
/// ```
|
/// ```
|
||||||
/// RealColumn get averageSpeed => real()();
|
/// RealColumn get averageSpeed => real()();
|
||||||
/// ```
|
/// ```
|
||||||
/// Note
|
|
||||||
@protected
|
@protected
|
||||||
RealColumnBuilder real() => null;
|
RealColumnBuilder real() => null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,9 @@ class InsertStatement<D extends DataClass> {
|
||||||
/// Otherwise, an exception will be thrown.
|
/// Otherwise, an exception will be thrown.
|
||||||
///
|
///
|
||||||
/// If the table contains an auto-increment column, the generated value will
|
/// If the table contains an auto-increment column, the generated value will
|
||||||
/// be returned.
|
/// be returned. If there is no auto-increment column, you can't rely on the
|
||||||
|
/// return value, but the future will resolve to an error when the insert
|
||||||
|
/// fails.
|
||||||
Future<int> insert(Insertable<D> entity, {bool orReplace = false}) async {
|
Future<int> insert(Insertable<D> entity, {bool orReplace = false}) async {
|
||||||
_validateIntegrity(entity);
|
_validateIntegrity(entity);
|
||||||
final ctx = _createContext(entity, orReplace);
|
final ctx = _createContext(entity, orReplace);
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
part of 'sql_types.dart';
|
||||||
|
|
||||||
|
/// Maps a custom dart object of type [D] into a primitive type [S] understood
|
||||||
|
/// by the sqlite backend.
|
||||||
|
///
|
||||||
|
/// Moor currently supports [DateTime], [double], [int], [Uint8List], [bool]
|
||||||
|
/// and [String] for [S].
|
||||||
|
abstract class TypeConverter<D, S> {
|
||||||
|
/// Empty constant constructor so that subclasses can have a constant
|
||||||
|
/// constructor.
|
||||||
|
const TypeConverter();
|
||||||
|
|
||||||
|
/// Map a value from an object in Dart into something that will be understood
|
||||||
|
/// by the database. Be aware that [value] is nullable.
|
||||||
|
S mapToSql(D value);
|
||||||
|
|
||||||
|
/// Maps a column from the database back to Dart. Be aware that [fromDb] is
|
||||||
|
/// nullable.
|
||||||
|
D mapToDart(S fromDb);
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
part 'custom_type.dart';
|
||||||
|
|
||||||
/// A type that can be mapped from Dart to sql. The generic type parameter here
|
/// A type that can be mapped from Dart to sql. The generic type parameter here
|
||||||
/// denotes the resolved dart type.
|
/// denotes the resolved dart type.
|
||||||
abstract class SqlType<T> {
|
abstract class SqlType<T> {
|
||||||
|
|
|
@ -50,6 +50,8 @@ class SharedTodos extends Table {
|
||||||
class TableWithoutPK extends Table {
|
class TableWithoutPK extends Table {
|
||||||
IntColumn get notReallyAnId => integer()();
|
IntColumn get notReallyAnId => integer()();
|
||||||
RealColumn get someFloat => real()();
|
RealColumn get someFloat => real()();
|
||||||
|
|
||||||
|
TextColumn get custom => text().map(const CustomConverter())();
|
||||||
}
|
}
|
||||||
|
|
||||||
class PureDefaults extends Table {
|
class PureDefaults extends Table {
|
||||||
|
@ -57,6 +59,25 @@ class PureDefaults extends Table {
|
||||||
TextColumn get txt => text().nullable()();
|
TextColumn get txt => text().nullable()();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// example object used for custom mapping
|
||||||
|
class MyCustomObject {
|
||||||
|
final String data;
|
||||||
|
MyCustomObject(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomConverter extends TypeConverter<MyCustomObject, String> {
|
||||||
|
const CustomConverter();
|
||||||
|
@override
|
||||||
|
MyCustomObject mapToDart(String fromDb) {
|
||||||
|
return fromDb == null ? null : MyCustomObject(fromDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String mapToSql(MyCustomObject value) {
|
||||||
|
return value?.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@UseMoor(
|
@UseMoor(
|
||||||
tables: [
|
tables: [
|
||||||
TodosTable,
|
TodosTable,
|
||||||
|
|
|
@ -842,18 +842,25 @@ class TableWithoutPKData extends DataClass
|
||||||
implements Insertable<TableWithoutPKData> {
|
implements Insertable<TableWithoutPKData> {
|
||||||
final int notReallyAnId;
|
final int notReallyAnId;
|
||||||
final double someFloat;
|
final double someFloat;
|
||||||
TableWithoutPKData({@required this.notReallyAnId, @required this.someFloat});
|
final MyCustomObject custom;
|
||||||
|
TableWithoutPKData(
|
||||||
|
{@required this.notReallyAnId,
|
||||||
|
@required this.someFloat,
|
||||||
|
@required this.custom});
|
||||||
factory TableWithoutPKData.fromData(
|
factory TableWithoutPKData.fromData(
|
||||||
Map<String, dynamic> data, GeneratedDatabase db,
|
Map<String, dynamic> data, GeneratedDatabase db,
|
||||||
{String prefix}) {
|
{String prefix}) {
|
||||||
final effectivePrefix = prefix ?? '';
|
final effectivePrefix = prefix ?? '';
|
||||||
final intType = db.typeSystem.forDartType<int>();
|
final intType = db.typeSystem.forDartType<int>();
|
||||||
final doubleType = db.typeSystem.forDartType<double>();
|
final doubleType = db.typeSystem.forDartType<double>();
|
||||||
|
final myCustomObjectType = db.typeSystem.forDartType<MyCustomObject>();
|
||||||
return TableWithoutPKData(
|
return TableWithoutPKData(
|
||||||
notReallyAnId: intType
|
notReallyAnId: intType
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}not_really_an_id']),
|
.mapFromDatabaseResponse(data['${effectivePrefix}not_really_an_id']),
|
||||||
someFloat: doubleType
|
someFloat: doubleType
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}some_float']),
|
.mapFromDatabaseResponse(data['${effectivePrefix}some_float']),
|
||||||
|
custom: myCustomObjectType
|
||||||
|
.mapFromDatabaseResponse(data['${effectivePrefix}custom']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
factory TableWithoutPKData.fromJson(Map<String, dynamic> json,
|
factory TableWithoutPKData.fromJson(Map<String, dynamic> json,
|
||||||
|
@ -861,6 +868,7 @@ class TableWithoutPKData extends DataClass
|
||||||
return TableWithoutPKData(
|
return TableWithoutPKData(
|
||||||
notReallyAnId: serializer.fromJson<int>(json['notReallyAnId']),
|
notReallyAnId: serializer.fromJson<int>(json['notReallyAnId']),
|
||||||
someFloat: serializer.fromJson<double>(json['someFloat']),
|
someFloat: serializer.fromJson<double>(json['someFloat']),
|
||||||
|
custom: serializer.fromJson<MyCustomObject>(json['custom']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
|
@ -869,6 +877,7 @@ class TableWithoutPKData extends DataClass
|
||||||
return {
|
return {
|
||||||
'notReallyAnId': serializer.toJson<int>(notReallyAnId),
|
'notReallyAnId': serializer.toJson<int>(notReallyAnId),
|
||||||
'someFloat': serializer.toJson<double>(someFloat),
|
'someFloat': serializer.toJson<double>(someFloat),
|
||||||
|
'custom': serializer.toJson<MyCustomObject>(custom),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,40 +891,49 @@ class TableWithoutPKData extends DataClass
|
||||||
someFloat: someFloat == null && nullToAbsent
|
someFloat: someFloat == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(someFloat),
|
: Value(someFloat),
|
||||||
|
custom:
|
||||||
|
custom == null && nullToAbsent ? const Value.absent() : Value(custom),
|
||||||
) as T;
|
) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
TableWithoutPKData copyWith({int notReallyAnId, double someFloat}) =>
|
TableWithoutPKData copyWith(
|
||||||
|
{int notReallyAnId, double someFloat, MyCustomObject custom}) =>
|
||||||
TableWithoutPKData(
|
TableWithoutPKData(
|
||||||
notReallyAnId: notReallyAnId ?? this.notReallyAnId,
|
notReallyAnId: notReallyAnId ?? this.notReallyAnId,
|
||||||
someFloat: someFloat ?? this.someFloat,
|
someFloat: someFloat ?? this.someFloat,
|
||||||
|
custom: custom ?? this.custom,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('TableWithoutPKData(')
|
return (StringBuffer('TableWithoutPKData(')
|
||||||
..write('notReallyAnId: $notReallyAnId, ')
|
..write('notReallyAnId: $notReallyAnId, ')
|
||||||
..write('someFloat: $someFloat')
|
..write('someFloat: $someFloat, ')
|
||||||
|
..write('custom: $custom')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => $mrjf($mrjc(
|
||||||
$mrjf($mrjc($mrjc(0, notReallyAnId.hashCode), someFloat.hashCode));
|
$mrjc($mrjc(0, notReallyAnId.hashCode), someFloat.hashCode),
|
||||||
|
custom.hashCode));
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is TableWithoutPKData &&
|
(other is TableWithoutPKData &&
|
||||||
other.notReallyAnId == notReallyAnId &&
|
other.notReallyAnId == notReallyAnId &&
|
||||||
other.someFloat == someFloat);
|
other.someFloat == someFloat &&
|
||||||
|
other.custom == custom);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TableWithoutPKCompanion extends UpdateCompanion<TableWithoutPKData> {
|
class TableWithoutPKCompanion extends UpdateCompanion<TableWithoutPKData> {
|
||||||
final Value<int> notReallyAnId;
|
final Value<int> notReallyAnId;
|
||||||
final Value<double> someFloat;
|
final Value<double> someFloat;
|
||||||
|
final Value<MyCustomObject> custom;
|
||||||
const TableWithoutPKCompanion({
|
const TableWithoutPKCompanion({
|
||||||
this.notReallyAnId = const Value.absent(),
|
this.notReallyAnId = const Value.absent(),
|
||||||
this.someFloat = const Value.absent(),
|
this.someFloat = const Value.absent(),
|
||||||
|
this.custom = const Value.absent(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -950,8 +968,20 @@ class $TableWithoutPKTable extends TableWithoutPK
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final VerificationMeta _customMeta = const VerificationMeta('custom');
|
||||||
|
GeneratedTextColumn _custom;
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [notReallyAnId, someFloat];
|
GeneratedTextColumn get custom => _custom ??= _constructCustom();
|
||||||
|
GeneratedTextColumn _constructCustom() {
|
||||||
|
return GeneratedTextColumn(
|
||||||
|
'custom',
|
||||||
|
$tableName,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [notReallyAnId, someFloat, custom];
|
||||||
@override
|
@override
|
||||||
$TableWithoutPKTable get asDslTable => this;
|
$TableWithoutPKTable get asDslTable => this;
|
||||||
@override
|
@override
|
||||||
|
@ -976,6 +1006,12 @@ class $TableWithoutPKTable extends TableWithoutPK
|
||||||
} else if (someFloat.isRequired && isInserting) {
|
} else if (someFloat.isRequired && isInserting) {
|
||||||
context.missing(_someFloatMeta);
|
context.missing(_someFloatMeta);
|
||||||
}
|
}
|
||||||
|
if (d.custom.present) {
|
||||||
|
context.handle(
|
||||||
|
_customMeta, custom.isAcceptableValue(d.custom.value, _customMeta));
|
||||||
|
} else if (custom.isRequired && isInserting) {
|
||||||
|
context.missing(_customMeta);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -996,6 +1032,9 @@ class $TableWithoutPKTable extends TableWithoutPK
|
||||||
if (d.someFloat.present) {
|
if (d.someFloat.present) {
|
||||||
map['some_float'] = Variable<double, RealType>(d.someFloat.value);
|
map['some_float'] = Variable<double, RealType>(d.someFloat.value);
|
||||||
}
|
}
|
||||||
|
if (d.custom.present) {
|
||||||
|
map['custom'] = Variable<MyCustomObject, StringType>(d.custom.value);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,8 @@ void main() {
|
||||||
|
|
||||||
test('can insert floating point values', () async {
|
test('can insert floating point values', () async {
|
||||||
// regression test for https://github.com/simolus3/moor/issues/30
|
// regression test for https://github.com/simolus3/moor/issues/30
|
||||||
await db
|
await db.into(db.tableWithoutPK).insert(
|
||||||
.into(db.tableWithoutPK)
|
TableWithoutPKData(notReallyAnId: 42, someFloat: 3.1415, custom: null));
|
||||||
.insert(TableWithoutPKData(notReallyAnId: 42, someFloat: 3.1415));
|
|
||||||
|
|
||||||
verify(executor.runInsert(
|
verify(executor.runInsert(
|
||||||
'INSERT INTO table_without_p_k '
|
'INSERT INTO table_without_p_k '
|
||||||
|
|
|
@ -16,11 +16,12 @@ class DaoGenerator extends GeneratorForAnnotation<UseDao> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
generateForAnnotatedElement(
|
generateForAnnotatedElement(
|
||||||
Element element, ConstantReader annotation, BuildStep buildStep) {
|
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
||||||
final tableTypes =
|
final tableTypes =
|
||||||
annotation.peek('tables').listValue.map((obj) => obj.toTypeValue());
|
annotation.peek('tables').listValue.map((obj) => obj.toTypeValue());
|
||||||
final parsedTables =
|
final parsedTables = await Stream.fromIterable(tableTypes)
|
||||||
tableTypes.map((type) => state.parseType(type, element)).toList();
|
.asyncMap((type) => state.parseType(type, element))
|
||||||
|
.toList();
|
||||||
final queries = annotation.peek('queries')?.mapValue ?? {};
|
final queries = annotation.peek('queries')?.mapValue ?? {};
|
||||||
|
|
||||||
if (element is! ClassElement) {
|
if (element is! ClassElement) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
import 'package:built_value/built_value.dart';
|
import 'package:built_value/built_value.dart';
|
||||||
|
|
||||||
part 'specified_column.g.dart';
|
part 'specified_column.g.dart';
|
||||||
|
@ -99,9 +100,27 @@ class SpecifiedColumn {
|
||||||
/// expression.
|
/// expression.
|
||||||
final Expression defaultArgument;
|
final Expression defaultArgument;
|
||||||
|
|
||||||
|
/// If a type converter has been specified as the argument of
|
||||||
|
/// ColumnBuilder.map, this contains the Dart code that references that type
|
||||||
|
/// converter.
|
||||||
|
final Expression typeConverter;
|
||||||
|
|
||||||
|
/// If the type of this column has been overridden, contains the actual Dart
|
||||||
|
/// type. Otherwise null.
|
||||||
|
///
|
||||||
|
/// Column types can be overridden with type converters. For instance, if
|
||||||
|
/// `C` was a type converter that converts `D` to `num`s, the column generated
|
||||||
|
/// by `real().map(const C())()` would have type `D` instead of `num`.
|
||||||
|
final DartType overriddenDartType;
|
||||||
|
|
||||||
/// The dart type that matches the values of this column. For instance, if a
|
/// The dart type that matches the values of this column. For instance, if a
|
||||||
/// table has declared an `IntColumn`, the matching dart type name would be [int].
|
/// table has declared an `IntColumn`, the matching dart type name would be [int].
|
||||||
String get dartTypeName => dartTypeNames[type];
|
String get dartTypeName {
|
||||||
|
if (overriddenDartType != null) {
|
||||||
|
return overriddenDartType.name;
|
||||||
|
}
|
||||||
|
return dartTypeNames[type];
|
||||||
|
}
|
||||||
|
|
||||||
/// The column type from the dsl library. For instance, if a table has
|
/// The column type from the dsl library. For instance, if a table has
|
||||||
/// declared an `IntColumn`, the matching dsl column name would also be an
|
/// declared an `IntColumn`, the matching dsl column name would also be an
|
||||||
|
@ -148,6 +167,8 @@ class SpecifiedColumn {
|
||||||
this.nullable = false,
|
this.nullable = false,
|
||||||
this.features = const [],
|
this.features = const [],
|
||||||
this.defaultArgument,
|
this.defaultArgument,
|
||||||
|
this.typeConverter,
|
||||||
|
this.overriddenDartType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ class MoorGenerator extends GeneratorForAnnotation<UseMoor> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
generateForAnnotatedElement(
|
generateForAnnotatedElement(
|
||||||
Element element, ConstantReader annotation, BuildStep buildStep) {
|
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
||||||
final tableTypes =
|
final tableTypes =
|
||||||
annotation.peek('tables').listValue.map((obj) => obj.toTypeValue());
|
annotation.peek('tables').listValue.map((obj) => obj.toTypeValue());
|
||||||
final daoTypes = annotation
|
final daoTypes = annotation
|
||||||
|
@ -33,7 +33,7 @@ class MoorGenerator extends GeneratorForAnnotation<UseMoor> {
|
||||||
var resolvedQueries = <SqlQuery>[];
|
var resolvedQueries = <SqlQuery>[];
|
||||||
|
|
||||||
for (var table in tableTypes) {
|
for (var table in tableTypes) {
|
||||||
tablesForThisDb.add(state.parseType(table, element));
|
tablesForThisDb.add(await state.parseType(table, element));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queries.isNotEmpty) {
|
if (queries.isNotEmpty) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
import 'package:moor_generator/src/errors.dart';
|
import 'package:moor_generator/src/errors.dart';
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
import 'package:moor_generator/src/model/specified_column.dart';
|
||||||
import 'package:moor_generator/src/parser/parser.dart';
|
import 'package:moor_generator/src/parser/parser.dart';
|
||||||
|
@ -20,7 +21,7 @@ final Set<String> starters = {
|
||||||
startBool,
|
startBool,
|
||||||
startDateTime,
|
startDateTime,
|
||||||
startBlob,
|
startBlob,
|
||||||
startReal
|
startReal,
|
||||||
};
|
};
|
||||||
|
|
||||||
const String _methodNamed = 'named';
|
const String _methodNamed = 'named';
|
||||||
|
@ -31,6 +32,7 @@ const String _methodWithLength = 'withLength';
|
||||||
const String _methodNullable = 'nullable';
|
const String _methodNullable = 'nullable';
|
||||||
const String _methodCustomConstraint = 'customConstraint';
|
const String _methodCustomConstraint = 'customConstraint';
|
||||||
const String _methodDefault = 'withDefault';
|
const String _methodDefault = 'withDefault';
|
||||||
|
const String _methodMap = 'map';
|
||||||
|
|
||||||
const String _errorMessage = 'This getter does not create a valid column that '
|
const String _errorMessage = 'This getter does not create a valid column that '
|
||||||
'can be parsed by moor. Please refer to the readme from moor to see how '
|
'can be parsed by moor. Please refer to the readme from moor to see how '
|
||||||
|
@ -49,6 +51,7 @@ class ColumnParser extends ParserBase {
|
||||||
we can extract what it means for the column (name, auto increment, PK,
|
we can extract what it means for the column (name, auto increment, PK,
|
||||||
constraints...).
|
constraints...).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final expr = returnExpressionOfMethod(getter);
|
final expr = returnExpressionOfMethod(getter);
|
||||||
|
|
||||||
if (!(expr is FunctionExpressionInvocation)) {
|
if (!(expr is FunctionExpressionInvocation)) {
|
||||||
|
@ -68,6 +71,8 @@ class ColumnParser extends ParserBase {
|
||||||
String foundExplicitName;
|
String foundExplicitName;
|
||||||
String foundCustomConstraint;
|
String foundCustomConstraint;
|
||||||
Expression foundDefaultExpression;
|
Expression foundDefaultExpression;
|
||||||
|
Expression foundTypeConverter;
|
||||||
|
DartType overrideDartType;
|
||||||
var wasDeclaredAsPrimaryKey = false;
|
var wasDeclaredAsPrimaryKey = false;
|
||||||
var nullable = false;
|
var nullable = false;
|
||||||
|
|
||||||
|
@ -148,6 +153,18 @@ class ColumnParser extends ParserBase {
|
||||||
final args = remainingExpr.argumentList;
|
final args = remainingExpr.argumentList;
|
||||||
final expression = args.arguments.single;
|
final expression = args.arguments.single;
|
||||||
foundDefaultExpression = expression;
|
foundDefaultExpression = expression;
|
||||||
|
break;
|
||||||
|
case _methodMap:
|
||||||
|
final args = remainingExpr.argumentList;
|
||||||
|
final expression = args.arguments.single;
|
||||||
|
|
||||||
|
// the map method has a parameter type that resolved to the runtime
|
||||||
|
// type of the custom object
|
||||||
|
final type = remainingExpr.typeArgumentTypes.single;
|
||||||
|
|
||||||
|
foundTypeConverter = expression;
|
||||||
|
overrideDartType = type;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're not at a starting method yet, so we need to go deeper!
|
// We're not at a starting method yet, so we need to go deeper!
|
||||||
|
@ -172,6 +189,8 @@ class ColumnParser extends ParserBase {
|
||||||
nullable: nullable,
|
nullable: nullable,
|
||||||
features: foundFeatures,
|
features: foundFeatures,
|
||||||
defaultArgument: foundDefaultExpression,
|
defaultArgument: foundDefaultExpression,
|
||||||
|
typeConverter: foundTypeConverter,
|
||||||
|
overriddenDartType: overrideDartType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,18 +13,18 @@ import 'package:moor/sqlite_keywords.dart';
|
||||||
class TableParser extends ParserBase {
|
class TableParser extends ParserBase {
|
||||||
TableParser(SharedState state) : super(state);
|
TableParser(SharedState state) : super(state);
|
||||||
|
|
||||||
SpecifiedTable parse(ClassElement element) {
|
Future<SpecifiedTable> parse(ClassElement element) async {
|
||||||
final sqlName = _parseTableName(element);
|
final sqlName = await _parseTableName(element);
|
||||||
if (sqlName == null) return null;
|
if (sqlName == null) return null;
|
||||||
|
|
||||||
final columns = _parseColumns(element);
|
final columns = await _parseColumns(element);
|
||||||
|
|
||||||
return SpecifiedTable(
|
return SpecifiedTable(
|
||||||
fromClass: element,
|
fromClass: element,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
sqlName: escapeIfNeeded(sqlName),
|
sqlName: escapeIfNeeded(sqlName),
|
||||||
dartTypeName: _readDartTypeName(element),
|
dartTypeName: _readDartTypeName(element),
|
||||||
primaryKey: _readPrimaryKey(element, columns),
|
primaryKey: await _readPrimaryKey(element, columns),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class TableParser extends ParserBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _parseTableName(ClassElement element) {
|
Future<String> _parseTableName(ClassElement element) async {
|
||||||
// todo allow override via a field (final String tableName = '') as well
|
// todo allow override via a field (final String tableName = '') as well
|
||||||
|
|
||||||
final tableNameGetter = element.getGetter('tableName');
|
final tableNameGetter = element.getGetter('tableName');
|
||||||
|
@ -52,7 +52,8 @@ class TableParser extends ParserBase {
|
||||||
|
|
||||||
// we expect something like get tableName => "myTableName", the getter
|
// we expect something like get tableName => "myTableName", the getter
|
||||||
// must do nothing more complicated
|
// must do nothing more complicated
|
||||||
final tableNameDeclaration = state.loadElementDeclaration(tableNameGetter);
|
final tableNameDeclaration =
|
||||||
|
await state.loadElementDeclaration(tableNameGetter);
|
||||||
final returnExpr = returnExpressionOfMethod(
|
final returnExpr = returnExpressionOfMethod(
|
||||||
tableNameDeclaration.node as MethodDeclaration);
|
tableNameDeclaration.node as MethodDeclaration);
|
||||||
|
|
||||||
|
@ -67,15 +68,15 @@ class TableParser extends ParserBase {
|
||||||
return tableName;
|
return tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<SpecifiedColumn> _readPrimaryKey(
|
Future<Set<SpecifiedColumn>> _readPrimaryKey(
|
||||||
ClassElement element, List<SpecifiedColumn> columns) {
|
ClassElement element, List<SpecifiedColumn> columns) async {
|
||||||
final primaryKeyGetter = element.getGetter('primaryKey');
|
final primaryKeyGetter = element.getGetter('primaryKey');
|
||||||
if (primaryKeyGetter == null) {
|
if (primaryKeyGetter == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ast = state.loadElementDeclaration(primaryKeyGetter).node
|
final resolved = await state.loadElementDeclaration(primaryKeyGetter);
|
||||||
as MethodDeclaration;
|
final ast = resolved.node as MethodDeclaration;
|
||||||
final body = ast.body;
|
final body = ast.body;
|
||||||
if (body is! ExpressionFunctionBody) {
|
if (body is! ExpressionFunctionBody) {
|
||||||
state.errors.add(MoorError(
|
state.errors.add(MoorError(
|
||||||
|
@ -110,12 +111,12 @@ class TableParser extends ParserBase {
|
||||||
return parsedPrimaryKey;
|
return parsedPrimaryKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SpecifiedColumn> _parseColumns(ClassElement element) {
|
Future<List<SpecifiedColumn>> _parseColumns(ClassElement element) {
|
||||||
return element.fields
|
return Stream.fromIterable(element.fields)
|
||||||
.where((field) => isColumn(field.type) && field.getter != null)
|
.where((field) => isColumn(field.type) && field.getter != null)
|
||||||
.map((field) {
|
.asyncMap((field) async {
|
||||||
final node =
|
final resolved = await state.loadElementDeclaration(field.getter);
|
||||||
state.loadElementDeclaration(field.getter).node as MethodDeclaration;
|
final node = resolved.node as MethodDeclaration;
|
||||||
|
|
||||||
return state.columnParser.parse(node, field.getter);
|
return state.columnParser.parse(node, field.getter);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
|
@ -21,21 +21,23 @@ class SharedState {
|
||||||
|
|
||||||
final tableTypeChecker = const TypeChecker.fromRuntime(Table);
|
final tableTypeChecker = const TypeChecker.fromRuntime(Table);
|
||||||
|
|
||||||
final Map<DartType, SpecifiedTable> foundTables = {};
|
final Map<DartType, Future<SpecifiedTable>> _foundTables = {};
|
||||||
|
|
||||||
SharedState(this.options) {
|
SharedState(this.options) {
|
||||||
tableParser = TableParser(this);
|
tableParser = TableParser(this);
|
||||||
columnParser = ColumnParser(this);
|
columnParser = ColumnParser(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementDeclarationResult loadElementDeclaration(Element element) {
|
Future<ElementDeclarationResult> loadElementDeclaration(
|
||||||
final result =
|
Element element) async {
|
||||||
element.library.session.getParsedLibraryByElement(element.library);
|
final resolvedLibrary = await element.library.session
|
||||||
return result.getElementDeclaration(element);
|
.getResolvedLibraryByElement(element.library);
|
||||||
|
|
||||||
|
return resolvedLibrary.getElementDeclaration(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpecifiedTable parseType(DartType type, Element initializedBy) {
|
Future<SpecifiedTable> parseType(DartType type, Element initializedBy) {
|
||||||
return foundTables.putIfAbsent(type, () {
|
return _foundTables.putIfAbsent(type, () {
|
||||||
if (!tableTypeChecker.isAssignableFrom(type.element)) {
|
if (!tableTypeChecker.isAssignableFrom(type.element)) {
|
||||||
errors.add(MoorError(
|
errors.add(MoorError(
|
||||||
critical: true,
|
critical: true,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
import 'package:moor_generator/src/model/specified_column.dart';
|
||||||
|
import 'package:moor_generator/src/model/specified_table.dart';
|
||||||
import 'package:moor_generator/src/options.dart';
|
import 'package:moor_generator/src/options.dart';
|
||||||
import 'package:moor_generator/src/parser/column_parser.dart';
|
import 'package:moor_generator/src/parser/column_parser.dart';
|
||||||
import 'package:moor_generator/src/parser/table_parser.dart';
|
import 'package:moor_generator/src/parser/table_parser.dart';
|
||||||
|
@ -56,46 +57,47 @@ void main() async {
|
||||||
..tableParser = TableParser(state);
|
..tableParser = TableParser(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<SpecifiedTable> parse(String name) {
|
||||||
|
return TableParser(state).parse(testLib.getType(name));
|
||||||
|
}
|
||||||
|
|
||||||
group('SQL table name', () {
|
group('SQL table name', () {
|
||||||
test('should parse correctly when valid', () {
|
test('should parse correctly when valid', () async {
|
||||||
expect(
|
final parsed = await parse('TableWithCustomName');
|
||||||
TableParser(state)
|
expect(parsed.sqlName, equals('my-fancy-table'));
|
||||||
.parse(testLib.getType('TableWithCustomName'))
|
|
||||||
.sqlName,
|
|
||||||
equals('my-fancy-table'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should use class name if table name is not specified', () {
|
test('should use class name if table name is not specified', () async {
|
||||||
expect(TableParser(state).parse(testLib.getType('Users')).sqlName,
|
final parsed = await parse('Users');
|
||||||
equals('users'));
|
expect(parsed.sqlName, equals('users'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not parse for complex methods', () async {
|
test('should not parse for complex methods', () async {
|
||||||
TableParser(state).parse(testLib.getType('WrongName'));
|
await TableParser(state).parse(testLib.getType('WrongName'));
|
||||||
|
|
||||||
expect(state.errors.errors, isNotEmpty);
|
expect(state.errors.errors, isNotEmpty);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Columns', () {
|
group('Columns', () {
|
||||||
test('should use field name if no name has been set explicitely', () {
|
test('should use field name if no name has been set explicitely', () async {
|
||||||
final table = TableParser(state).parse(testLib.getType('Users'));
|
final table = await parse('Users');
|
||||||
final idColumn =
|
final idColumn =
|
||||||
table.columns.singleWhere((col) => col.name.name == 'id');
|
table.columns.singleWhere((col) => col.name.name == 'id');
|
||||||
|
|
||||||
expect(idColumn.name, equals(ColumnName.implicitly('id')));
|
expect(idColumn.name, equals(ColumnName.implicitly('id')));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should use explicit name, if it exists', () {
|
test('should use explicit name, if it exists', () async {
|
||||||
final table = TableParser(state).parse(testLib.getType('Users'));
|
final table = await parse('Users');
|
||||||
final idColumn =
|
final idColumn =
|
||||||
table.columns.singleWhere((col) => col.name.name == 'user_name');
|
table.columns.singleWhere((col) => col.name.name == 'user_name');
|
||||||
|
|
||||||
expect(idColumn.name, equals(ColumnName.explicitly('user_name')));
|
expect(idColumn.name, equals(ColumnName.explicitly('user_name')));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should parse min and max length for text columns', () {
|
test('should parse min and max length for text columns', () async {
|
||||||
final table = TableParser(state).parse(testLib.getType('Users'));
|
final table = await parse('Users');
|
||||||
final idColumn =
|
final idColumn =
|
||||||
table.columns.singleWhere((col) => col.name.name == 'user_name');
|
table.columns.singleWhere((col) => col.name.name == 'user_name');
|
||||||
|
|
||||||
|
@ -103,8 +105,8 @@ void main() async {
|
||||||
contains(LimitingTextLength.withLength(min: 6, max: 32)));
|
contains(LimitingTextLength.withLength(min: 6, max: 32)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should only parse max length when relevant', () {
|
test('should only parse max length when relevant', () async {
|
||||||
final table = TableParser(state).parse(testLib.getType('Users'));
|
final table = await parse('Users');
|
||||||
final idColumn =
|
final idColumn =
|
||||||
table.columns.singleWhere((col) => col.dartGetterName == 'onlyMax');
|
table.columns.singleWhere((col) => col.dartGetterName == 'onlyMax');
|
||||||
|
|
||||||
|
@ -112,9 +114,8 @@ void main() async {
|
||||||
idColumn.features, contains(LimitingTextLength.withLength(max: 100)));
|
idColumn.features, contains(LimitingTextLength.withLength(max: 100)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses custom constraints', () {
|
test('parses custom constraints', () async {
|
||||||
final table =
|
final table = await parse('CustomPrimaryKey');
|
||||||
TableParser(state).parse(testLib.getType('CustomPrimaryKey'));
|
|
||||||
|
|
||||||
final partA =
|
final partA =
|
||||||
table.columns.singleWhere((c) => c.dartGetterName == 'partA');
|
table.columns.singleWhere((c) => c.dartGetterName == 'partA');
|
||||||
|
@ -125,8 +126,8 @@ void main() async {
|
||||||
expect(partA.customConstraints, isNull);
|
expect(partA.customConstraints, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parsed default values', () {
|
test('parsed default values', () async {
|
||||||
final table = TableParser(state).parse(testLib.getType('Users'));
|
final table = await parse('Users');
|
||||||
final defaultsColumn =
|
final defaultsColumn =
|
||||||
table.columns.singleWhere((c) => c.name.name == 'defaults');
|
table.columns.singleWhere((c) => c.name.name == 'defaults');
|
||||||
|
|
||||||
|
@ -134,8 +135,8 @@ void main() async {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses custom primary keys', () {
|
test('parses custom primary keys', () async {
|
||||||
final table = TableParser(state).parse(testLib.getType('CustomPrimaryKey'));
|
final table = await parse('CustomPrimaryKey');
|
||||||
|
|
||||||
expect(table.primaryKey, containsAll(table.columns));
|
expect(table.primaryKey, containsAll(table.columns));
|
||||||
expect(table.columns.any((column) => column.hasAI), isFalse);
|
expect(table.columns.any((column) => column.hasAI), isFalse);
|
||||||
|
|
Loading…
Reference in New Issue