Skip to content

Commit

Permalink
Change ForeignKeyAction to enum in the generator (#359)
Browse files Browse the repository at this point in the history
  • Loading branch information
mqus authored Nov 22, 2020
1 parent c5925af commit 1a49c0f
Show file tree
Hide file tree
Showing 16 changed files with 222 additions and 90 deletions.
16 changes: 8 additions & 8 deletions floor_annotation/lib/src/foreign_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class ForeignKey {
/// Action to take when the parent [Entity] is updated from the database.
///
/// By default, [ForeignKeyAction.noAction] is used.
final int onUpdate;
final ForeignKeyAction onUpdate;

/// [ForeignKeyAction]
/// Action to take when the parent [Entity] is deleted from the database.
///
/// By default, [ForeignKeyAction.noAction] is used.
final int onDelete;
final ForeignKeyAction onDelete;

/// Declares a foreign key on another [Entity].
const ForeignKey({
Expand All @@ -35,13 +35,13 @@ class ForeignKey {

/// Constants definition for values that can be used in
/// [ForeignKey.onDelete] and [ForeignKey.onUpdate]
abstract class ForeignKeyAction {
enum ForeignKeyAction {
/// Possible value for [ForeignKey.onDelete] or [ForeignKey.onUpdate].
///
/// When a parent key is modified or deleted from the database, no special
/// action is taken. This means that SQLite will not make any effort to fix
/// the constraint failure, instead, reject the change.
static const noAction = 1;
noAction,

/// Possible value for [ForeignKey.onDelete] or [ForeignKey.onUpdate].
///
Expand All @@ -57,22 +57,22 @@ abstract class ForeignKeyAction {
/// Even if the foreign key constraint it is attached to is deferred(),
/// configuring a RESTRICT action causes SQLite to return an error immediately
/// if a parent key with dependent child keys is deleted or modified.
static const restrict = 2;
restrict,

/// Possible value for [ForeignKey.onDelete] or [ForeignKey.onUpdate].
///
/// If the configured action is 'SET NULL', then when a parent key is deleted
/// (for [ForeignKey.onDelete]) or modified (for [ForeignKey.onUpdate]), the
/// child key columns of all rows in the child table that mapped to the parent
/// key are set to contain NULL values.
static const setNull = 3;
setNull,

/// Possible value for [ForeignKey.onDelete] or [ForeignKey.onUpdate].
///
/// The 'SET DEFAULT' actions are similar to SET_NULL, except that each of the
/// child key columns is set to contain the columns default value instead of
/// NULL.
static const setDefault = 4;
setDefault,

/// Possible value for [ForeignKey.onDelete] or [ForeignKey.onUpdate].
///
Expand All @@ -82,5 +82,5 @@ abstract class ForeignKeyAction {
/// deleted parent row is also deleted. For an [ForeignKey.onUpdate] action,
/// it means that the values stored in each dependent child key are modified
/// to match the new parent key values.
static const cascade = 5;
cascade,
}
27 changes: 24 additions & 3 deletions floor_generator/lib/misc/extension/dart_object_extension.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:floor_annotation/floor_annotation.dart';
import 'package:meta/meta.dart';

extension DartObjectExtension on DartObject {
String toEnumValueString() {
/// get the String representation of the enum value, or the result of
/// [orElse] if the enum was not valid.
String toEnumValueString({@required String orElse()}) {
final interfaceType = type as InterfaceType;
final enumName = interfaceType.getDisplayString(withNullability: false);
final enumValue = interfaceType.element.fields
.where((element) => element.isEnumConstant)
.map((fieldElement) => fieldElement.name)
.singleWhere((valueName) => getField(valueName) != null);
.singleWhere((valueName) => getField(valueName) != null,
orElse: () => null);
if (enumValue == null) {
return orElse();
} else {
return '$enumName.$enumValue';
}
}

return '$enumName.$enumValue';
/// get the ForeignKeyAction this enum represents, or the result of
/// [orElse] if the enum did not contain a valid value.
ForeignKeyAction toForeignKeyAction({@required ForeignKeyAction orElse()}) {
final enumValueString = toEnumValueString(orElse: () => null);
if (enumValueString == null) {
return orElse();
} else {
return ForeignKeyAction.values.singleWhere(
(foreignKeyAction) => foreignKeyAction.toString() == enumValueString,
orElse: orElse);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import 'package:floor_annotation/floor_annotation.dart';
import 'package:floor_generator/misc/annotations.dart';

abstract class ForeignKeyAction {
static const noAction = 1;
static const restrict = 2;
static const setNull = 3;
static const setDefault = 4;
static const cascade = 5;

extension ForeignKeyActionExtension on ForeignKeyAction {
@nonNull
static String getString(final int action) {
switch (action) {
String toSql() {
switch (this) {
case ForeignKeyAction.noAction:
return 'NO ACTION';
case ForeignKeyAction.restrict:
return 'RESTRICT';
case ForeignKeyAction.setNull:
Expand All @@ -18,9 +15,9 @@ abstract class ForeignKeyAction {
return 'SET DEFAULT';
case ForeignKeyAction.cascade:
return 'CASCADE';
case ForeignKeyAction.noAction:
default:
return 'NO ACTION';
default: // can only match null
throw ArgumentError('toSql() should not be called on a null value. '
'This is a bug in floor.');
}
}
}
26 changes: 19 additions & 7 deletions floor_generator/lib/processor/entity_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import 'package:dartx/dartx.dart';
import 'package:floor_annotation/floor_annotation.dart' as annotations;
import 'package:floor_generator/misc/annotations.dart';
import 'package:floor_generator/misc/constants.dart';
import 'package:floor_generator/misc/extension/dart_object_extension.dart';
import 'package:floor_generator/misc/extension/type_converters_extension.dart';
import 'package:floor_generator/misc/foreign_key_action.dart';
import 'package:floor_generator/misc/type_utils.dart';
import 'package:floor_generator/processor/error/entity_processor_error.dart';
import 'package:floor_generator/processor/queryable_processor.dart';
Expand Down Expand Up @@ -92,13 +92,11 @@ class EntityProcessor extends QueryableProcessor<Entity> {
throw _processorError.missingParentColumns;
}

final onUpdateAnnotationValue =
foreignKeyObject.getField(ForeignKeyField.onUpdate)?.toIntValue();
final onUpdate = ForeignKeyAction.getString(onUpdateAnnotationValue);
final onUpdate =
_getForeignKeyAction(foreignKeyObject, ForeignKeyField.onUpdate);

final onDeleteAnnotationValue =
foreignKeyObject.getField(ForeignKeyField.onDelete)?.toIntValue();
final onDelete = ForeignKeyAction.getString(onDeleteAnnotationValue);
final onDelete =
_getForeignKeyAction(foreignKeyObject, ForeignKeyField.onDelete);

return ForeignKey(
parentName,
Expand Down Expand Up @@ -267,4 +265,18 @@ class EntityProcessor extends QueryableProcessor<Entity> {
return attributeValue;
}
}

@nonNull
annotations.ForeignKeyAction _getForeignKeyAction(
DartObject foreignKeyObject, String triggerName) {
final field = foreignKeyObject.getField(triggerName);
if (field == null) {
// field was not defined, return default value
return annotations.ForeignKeyAction.noAction;
}

return field.toForeignKeyAction(
orElse: () =>
throw _processorError.wrongForeignKeyAction(field, triggerName));
}
}
11 changes: 11 additions & 0 deletions floor_generator/lib/processor/error/entity_processor_error.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:source_gen/source_gen.dart';

Expand Down Expand Up @@ -70,6 +71,16 @@ class EntityProcessorError {
);
}

InvalidGenerationSourceError wrongForeignKeyAction(
DartObject field, String triggerName) {
return InvalidGenerationSourceError(
'No ForeignKeyAction with the value $field exists for the $triggerName trigger.',
todo:
'Make sure to add a correct ForeignKeyAction like `ForeignKeyAction.noAction` or leave it out entirely.',
element: _classElement,
);
}

InvalidGenerationSourceError get autoIncrementInWithoutRowid {
return InvalidGenerationSourceError(
'autoGenerate is not allowed in WITHOUT ROWID tables',
Expand Down
8 changes: 6 additions & 2 deletions floor_generator/lib/processor/insertion_method_processor.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:floor_annotation/floor_annotation.dart' as annotations
show Insert;
show Insert, OnConflictStrategy;
import 'package:floor_generator/misc/annotations.dart';
import 'package:floor_generator/misc/change_method_processor_helper.dart';
import 'package:floor_generator/misc/constants.dart';
Expand Down Expand Up @@ -87,7 +87,11 @@ class InsertionMethodProcessor implements Processor<InsertionMethod> {
return _methodElement
.getAnnotation(annotations.Insert)
.getField(AnnotationField.onConflict)
.toEnumValueString();
.toEnumValueString(
orElse: () => throw InvalidGenerationSourceError(
'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}',
element: _methodElement,
));
}

void _assertMethodReturnsFuture(final DartType returnType) {
Expand Down
8 changes: 6 additions & 2 deletions floor_generator/lib/processor/update_method_processor.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:floor_annotation/floor_annotation.dart' as annotations
show Update;
show Update, OnConflictStrategy;
import 'package:floor_generator/misc/annotations.dart';
import 'package:floor_generator/misc/change_method_processor_helper.dart';
import 'package:floor_generator/misc/constants.dart';
Expand Down Expand Up @@ -69,7 +69,11 @@ class UpdateMethodProcessor implements Processor<UpdateMethod> {
return _methodElement
.getAnnotation(annotations.Update)
.getField(AnnotationField.onConflict)
.toEnumValueString();
.toEnumValueString(
orElse: () => throw InvalidGenerationSourceError(
'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}',
element: _methodElement,
));
}

@nonNull
Expand Down
10 changes: 6 additions & 4 deletions floor_generator/lib/value_object/foreign_key.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'package:collection/collection.dart';
import 'package:floor_annotation/floor_annotation.dart' show ForeignKeyAction;
import 'package:floor_generator/misc/annotations.dart';
import 'package:floor_generator/misc/extension/foreign_key_action_extension.dart';

class ForeignKey {
final String parentName;
final List<String> parentColumns;
final List<String> childColumns;
final String onUpdate;
final String onDelete;
final ForeignKeyAction onUpdate;
final ForeignKeyAction onDelete;

ForeignKey(
this.parentName,
Expand All @@ -25,8 +27,8 @@ class ForeignKey {

return 'FOREIGN KEY ($escapedChildColumns)'
' REFERENCES `$parentName` ($escapedParentColumns)'
' ON UPDATE $onUpdate'
' ON DELETE $onDelete';
' ON UPDATE ${onUpdate.toSql()}'
' ON DELETE ${onDelete.toSql()}';
}

final _listEquality = const ListEquality<String>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:floor_annotation/floor_annotation.dart' as annotations;
import 'package:floor_generator/misc/extension/foreign_key_action_extension.dart';
import 'package:test/test.dart';

void main() {
group('foreign key action strings', () {
test('NO ACTION', () {
final actual = annotations.ForeignKeyAction.noAction.toSql();
expect(actual, equals('NO ACTION'));
});

test('RESTRICT', () {
final actual = annotations.ForeignKeyAction.restrict.toSql();
expect(actual, equals('RESTRICT'));
});

test('SET NULL', () {
final actual = annotations.ForeignKeyAction.setNull.toSql();
expect(actual, equals('SET NULL'));
});

test('SET DEFAULT', () {
final actual = annotations.ForeignKeyAction.setDefault.toSql();
expect(actual, equals('SET DEFAULT'));
});

test('CASCADE', () {
final actual = annotations.ForeignKeyAction.cascade.toSql();
expect(actual, equals('CASCADE'));
});

test('null throws ArgumentError', () {
expect(() => null.toSql(), throwsArgumentError);
});
});
}
42 changes: 0 additions & 42 deletions floor_generator/test/misc/foreign_key_action_test.dart

This file was deleted.

6 changes: 6 additions & 0 deletions floor_generator/test/mocks.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:mockito/mockito.dart';
Expand All @@ -7,3 +8,8 @@ class MockClassElement extends Mock implements ClassElement {}
class MockFieldElement extends Mock implements FieldElement {}

class MockDartType extends Mock implements DartType {}

class MockDartObject extends Mock implements DartObject {
@override
String toString() => 'Null (null)';
}
Loading

0 comments on commit 1a49c0f

Please sign in to comment.