mirror of https://github.com/AMT-Cheif/drift.git
Merge pull request #1492 from westito/generate_tocompanion
Add 'toCompanion' generator for custom row classes
This commit is contained in:
commit
38a8ad136f
|
@ -155,9 +155,17 @@ class UseRowClass {
|
||||||
/// used to map database rows to the desired row class.
|
/// used to map database rows to the desired row class.
|
||||||
final String constructor;
|
final String constructor;
|
||||||
|
|
||||||
|
/// Generate a `toInsertable()` extension function for [type] mapping all
|
||||||
|
/// fields to an insertable object.
|
||||||
|
///
|
||||||
|
/// This can be useful when a custom data class should be used for inserts or
|
||||||
|
/// updates.
|
||||||
|
final bool generateInsertable;
|
||||||
|
|
||||||
/// Customize the class used by drift to hold an instance of an annotated
|
/// Customize the class used by drift to hold an instance of an annotated
|
||||||
/// table.
|
/// table.
|
||||||
///
|
///
|
||||||
/// For details, see the overall documentation on [UseRowClass].
|
/// For details, see the overall documentation on [UseRowClass].
|
||||||
const UseRowClass(this.type, {this.constructor = ''});
|
const UseRowClass(this.type,
|
||||||
|
{this.constructor = '', this.generateInsertable = false});
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ class SharedTodos extends Table {
|
||||||
|
|
||||||
const _uuid = Uuid();
|
const _uuid = Uuid();
|
||||||
|
|
||||||
@UseRowClass(CustomRowClass, constructor: 'map')
|
@UseRowClass(CustomRowClass, constructor: 'map', generateInsertable: true)
|
||||||
class TableWithoutPK extends Table {
|
class TableWithoutPK extends Table {
|
||||||
IntColumn get notReallyAnId => integer()();
|
IntColumn get notReallyAnId => integer()();
|
||||||
RealColumn get someFloat => real()();
|
RealColumn get someFloat => real()();
|
||||||
|
@ -67,13 +67,15 @@ class TableWithoutPK extends Table {
|
||||||
text().map(const CustomConverter()).clientDefault(_uuid.v4)();
|
text().map(const CustomConverter()).clientDefault(_uuid.v4)();
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomRowClass implements Insertable<CustomRowClass> {
|
class CustomRowClass {
|
||||||
final int notReallyAnId;
|
final int notReallyAnId;
|
||||||
final double anotherName;
|
final double anotherName;
|
||||||
final MyCustomObject custom;
|
final MyCustomObject custom;
|
||||||
|
|
||||||
final String? notFromDb;
|
final String? notFromDb;
|
||||||
|
|
||||||
|
double get someFloat => anotherName;
|
||||||
|
|
||||||
CustomRowClass._(
|
CustomRowClass._(
|
||||||
this.notReallyAnId, this.anotherName, this.custom, this.notFromDb);
|
this.notReallyAnId, this.anotherName, this.custom, this.notFromDb);
|
||||||
|
|
||||||
|
@ -81,15 +83,6 @@ class CustomRowClass implements Insertable<CustomRowClass> {
|
||||||
{required MyCustomObject custom, String? notFromDb}) {
|
{required MyCustomObject custom, String? notFromDb}) {
|
||||||
return CustomRowClass._(notReallyAnId, someFloat, custom, notFromDb);
|
return CustomRowClass._(notReallyAnId, someFloat, custom, notFromDb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
|
||||||
return TableWithoutPKCompanion(
|
|
||||||
notReallyAnId: Value(notReallyAnId),
|
|
||||||
someFloat: Value(anotherName),
|
|
||||||
custom: Value(custom),
|
|
||||||
).toColumns(nullToAbsent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PureDefaults extends Table {
|
class PureDefaults extends Table {
|
||||||
|
|
|
@ -1054,6 +1054,27 @@ class TableWithoutPKCompanion extends UpdateCompanion<CustomRowClass> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _$CustomRowClassInsertable implements Insertable<CustomRowClass> {
|
||||||
|
CustomRowClass _object;
|
||||||
|
|
||||||
|
_$CustomRowClassInsertable(this._object);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
return TableWithoutPKCompanion(
|
||||||
|
notReallyAnId: Value(_object.notReallyAnId),
|
||||||
|
someFloat: Value(_object.someFloat),
|
||||||
|
custom: Value(_object.custom),
|
||||||
|
).toColumns(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CustomRowClassToInsertable on CustomRowClass {
|
||||||
|
_$CustomRowClassInsertable toInsertable() {
|
||||||
|
return _$CustomRowClassInsertable(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class $TableWithoutPKTable extends TableWithoutPK
|
class $TableWithoutPKTable extends TableWithoutPK
|
||||||
with TableInfo<$TableWithoutPKTable, CustomRowClass> {
|
with TableInfo<$TableWithoutPKTable, CustomRowClass> {
|
||||||
final GeneratedDatabase _db;
|
final GeneratedDatabase _db;
|
||||||
|
|
|
@ -31,7 +31,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.into(db.tableWithoutPK).insert(
|
await db.into(db.tableWithoutPK).insert(
|
||||||
CustomRowClass.map(42, 3.1415, custom: MyCustomObject('custom')));
|
CustomRowClass.map(42, 3.1415, custom: MyCustomObject('custom'))
|
||||||
|
.toInsertable());
|
||||||
|
|
||||||
verify(executor.runInsert(
|
verify(executor.runInsert(
|
||||||
'INSERT INTO table_without_p_k '
|
'INSERT INTO table_without_p_k '
|
||||||
|
|
|
@ -15,8 +15,12 @@ class FoundDartClass {
|
||||||
FoundDartClass(this.classElement, this.instantiation);
|
FoundDartClass(this.classElement, this.instantiation);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExistingRowClass? validateExistingClass(Iterable<MoorColumn> columns,
|
ExistingRowClass? validateExistingClass(
|
||||||
FoundDartClass dartClass, String constructor, ErrorSink errors) {
|
Iterable<MoorColumn> columns,
|
||||||
|
FoundDartClass dartClass,
|
||||||
|
String constructor,
|
||||||
|
bool generateInsertable,
|
||||||
|
ErrorSink errors) {
|
||||||
final desiredClass = dartClass.classElement;
|
final desiredClass = dartClass.classElement;
|
||||||
ConstructorElement? ctor;
|
ConstructorElement? ctor;
|
||||||
|
|
||||||
|
@ -52,8 +56,22 @@ ExistingRowClass? validateExistingClass(Iterable<MoorColumn> columns,
|
||||||
for (final parameter in ctor.parameters) {
|
for (final parameter in ctor.parameters) {
|
||||||
final column = unmatchedColumnsByName.remove(parameter.name);
|
final column = unmatchedColumnsByName.remove(parameter.name);
|
||||||
if (column != null) {
|
if (column != null) {
|
||||||
columnsToParameter[column] = parameter;
|
final matchField = !generateInsertable ||
|
||||||
_checkType(parameter, column, errors);
|
dartClass.classElement.fields
|
||||||
|
.any((field) => field.name == parameter.name);
|
||||||
|
if (matchField) {
|
||||||
|
columnsToParameter[column] = parameter;
|
||||||
|
_checkType(parameter, column, errors);
|
||||||
|
} else {
|
||||||
|
final error = ErrorInDartCode(
|
||||||
|
affectedElement: parameter,
|
||||||
|
severity: Severity.criticalError,
|
||||||
|
message: 'Constructor parameter ${parameter.name} has no matching '
|
||||||
|
'field. When "generateInsertable" enabled, all constructor '
|
||||||
|
'parameter must have a matching field. Alternatively, you can '
|
||||||
|
'declare a getter field.');
|
||||||
|
throw Exception(error);
|
||||||
|
}
|
||||||
} else if (!parameter.isOptional) {
|
} else if (!parameter.isOptional) {
|
||||||
errors.report(ErrorInDartCode(
|
errors.report(ErrorInDartCode(
|
||||||
affectedElement: parameter,
|
affectedElement: parameter,
|
||||||
|
@ -63,7 +81,8 @@ ExistingRowClass? validateExistingClass(Iterable<MoorColumn> columns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExistingRowClass(desiredClass, ctor, columnsToParameter,
|
return ExistingRowClass(
|
||||||
|
desiredClass, ctor, columnsToParameter, generateInsertable,
|
||||||
typeInstantiation: dartClass.instantiation ?? const []);
|
typeInstantiation: dartClass.instantiation ?? const []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ class TableParser {
|
||||||
String name;
|
String name;
|
||||||
FoundDartClass? existingClass;
|
FoundDartClass? existingClass;
|
||||||
String? constructorInExistingClass;
|
String? constructorInExistingClass;
|
||||||
|
bool? generateInsertable;
|
||||||
|
|
||||||
if (dataClassName != null) {
|
if (dataClassName != null) {
|
||||||
name = dataClassName.getField('name')!.toStringValue()!;
|
name = dataClassName.getField('name')!.toStringValue()!;
|
||||||
|
@ -81,6 +82,8 @@ class TableParser {
|
||||||
final type = useRowClass.getField('type')!.toTypeValue();
|
final type = useRowClass.getField('type')!.toTypeValue();
|
||||||
constructorInExistingClass =
|
constructorInExistingClass =
|
||||||
useRowClass.getField('constructor')!.toStringValue()!;
|
useRowClass.getField('constructor')!.toStringValue()!;
|
||||||
|
generateInsertable =
|
||||||
|
useRowClass.getField('generateInsertable')!.toBoolValue()!;
|
||||||
|
|
||||||
if (type is InterfaceType) {
|
if (type is InterfaceType) {
|
||||||
existingClass = FoundDartClass(type.element, type.typeArguments);
|
existingClass = FoundDartClass(type.element, type.typeArguments);
|
||||||
|
@ -96,7 +99,7 @@ class TableParser {
|
||||||
final verified = existingClass == null
|
final verified = existingClass == null
|
||||||
? null
|
? null
|
||||||
: validateExistingClass(columns, existingClass,
|
: validateExistingClass(columns, existingClass,
|
||||||
constructorInExistingClass!, base.step.errors);
|
constructorInExistingClass!, generateInsertable!, base.step.errors);
|
||||||
return _DataClassInformation(name, verified);
|
return _DataClassInformation(name, verified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -181,7 +181,7 @@ class CreateTableReader {
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
existingRowClass = validateExistingClass(
|
existingRowClass = validateExistingClass(
|
||||||
foundColumns.values, clazz, '', step.errors);
|
foundColumns.values, clazz, '', false, step.errors);
|
||||||
dataClassName = existingRowClass?.targetClass.name;
|
dataClassName = existingRowClass?.targetClass.name;
|
||||||
}
|
}
|
||||||
} else if (overriddenNames.contains('/')) {
|
} else if (overriddenNames.contains('/')) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ class ViewAnalyzer extends BaseAnalyzer {
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
final rowClass = view.existingRowClass =
|
final rowClass = view.existingRowClass =
|
||||||
validateExistingClass(columns, clazz, '', step.errors);
|
validateExistingClass(columns, clazz, '', false, step.errors);
|
||||||
final newName = rowClass?.targetClass.name;
|
final newName = rowClass?.targetClass.name;
|
||||||
if (newName != null) {
|
if (newName != null) {
|
||||||
view.dartTypeName = rowClass!.targetClass.name;
|
view.dartTypeName = rowClass!.targetClass.name;
|
||||||
|
|
|
@ -69,7 +69,11 @@ class ExistingRowClass {
|
||||||
final ConstructorElement constructor;
|
final ConstructorElement constructor;
|
||||||
final Map<MoorColumn, ParameterElement> mapping;
|
final Map<MoorColumn, ParameterElement> mapping;
|
||||||
|
|
||||||
ExistingRowClass(this.targetClass, this.constructor, this.mapping,
|
/// Generate toCompanion for data class
|
||||||
|
final bool generateInsertable;
|
||||||
|
|
||||||
|
ExistingRowClass(
|
||||||
|
this.targetClass, this.constructor, this.mapping, this.generateInsertable,
|
||||||
{this.typeInstantiation = const []});
|
{this.typeInstantiation = const []});
|
||||||
|
|
||||||
String dartType([GenerationOptions options = const GenerationOptions()]) {
|
String dartType([GenerationOptions options = const GenerationOptions()]) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift_dev/moor_generator.dart';
|
import 'package:drift_dev/moor_generator.dart';
|
||||||
import 'package:drift_dev/src/utils/string_escaper.dart';
|
import 'package:drift_dev/src/utils/string_escaper.dart';
|
||||||
import 'package:drift_dev/src/writer/utils/override_toString.dart';
|
import 'package:drift_dev/src/writer/utils/override_toString.dart';
|
||||||
|
@ -28,6 +29,10 @@ class UpdateCompanionWriter {
|
||||||
_writeToString();
|
_writeToString();
|
||||||
|
|
||||||
_buffer.write('}\n');
|
_buffer.write('}\n');
|
||||||
|
|
||||||
|
if (table.existingRowClass?.generateInsertable ?? false) {
|
||||||
|
_writeToCompanionExtension();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeFields() {
|
void _writeFields() {
|
||||||
|
@ -211,4 +216,39 @@ class UpdateCompanionWriter {
|
||||||
_buffer,
|
_buffer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _writeToCompanionExtension() {
|
||||||
|
final info = table.existingRowClass;
|
||||||
|
if (info == null) return;
|
||||||
|
|
||||||
|
final companionName = table.getNameForCompanionClass(scope.options);
|
||||||
|
final className = table.dartTypeName;
|
||||||
|
final insertableClass = '_\$${className}Insertable';
|
||||||
|
|
||||||
|
_buffer.write('class $insertableClass implements '
|
||||||
|
'Insertable<$className> {\n'
|
||||||
|
'$className _object;\n\n'
|
||||||
|
'$insertableClass(this._object);\n\n'
|
||||||
|
'@override\n'
|
||||||
|
'Map<String, Expression> toColumns(bool nullToAbsent) {\n'
|
||||||
|
'return $companionName(\n');
|
||||||
|
|
||||||
|
for (final field in info.mapping.values) {
|
||||||
|
final column =
|
||||||
|
table.columns.firstWhereOrNull((e) => e.dartGetterName == field.name);
|
||||||
|
|
||||||
|
if (column != null) {
|
||||||
|
final dartName = column.dartGetterName;
|
||||||
|
_buffer.write('$dartName: Value (_object.$dartName),\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer
|
||||||
|
..write(').toColumns(false);\n}\n}\n\n')
|
||||||
|
..write('extension ${table.dartTypeName}ToInsertable '
|
||||||
|
'on ${table.dartTypeName} {')
|
||||||
|
..write('$insertableClass toInsertable() {\n')
|
||||||
|
..write('return _\$${className}Insertable(this);\n')
|
||||||
|
..write('}\n}\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue