Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change ForeignKeyAction to enum in the generator #359

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
mqus marked this conversation as resolved.
Show resolved Hide resolved
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);
vitusortner marked this conversation as resolved.
Show resolved Hide resolved
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