diff --git a/drift/lib/src/dsl/table.dart b/drift/lib/src/dsl/table.dart index ba43f10d..c923afd4 100644 --- a/drift/lib/src/dsl/table.dart +++ b/drift/lib/src/dsl/table.dart @@ -139,7 +139,7 @@ abstract class Table extends HasResultSet { /// corresponding to the enum's index. Note that this can invalidate your data /// if you add another value to the enum class. @protected - ColumnBuilder intEnum() => _isGenerated(); + ColumnBuilder intEnum() => _isGenerated(); /// Use this as the body of a getter to declare a column that holds strings. /// Example (inside the body of a table class): @@ -149,6 +149,13 @@ abstract class Table extends HasResultSet { @protected ColumnBuilder text() => _isGenerated(); + /// Creates a column to store an `enum` class [T]. + /// + /// In the database, the column will be represented as text corresponding to + /// the name of the enum entries. Note that this can invalidate your data if + /// you rename the entries of the enum class. + ColumnBuilder textEnum() => _isGenerated(); + /// Use this as the body of a getter to declare a column that holds bools. /// Example (inside the body of a table class): /// ``` diff --git a/drift/lib/src/runtime/types/converters.dart b/drift/lib/src/runtime/types/converters.dart index a6b3011b..febc0499 100644 --- a/drift/lib/src/runtime/types/converters.dart +++ b/drift/lib/src/runtime/types/converters.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; + import '../../dsl/dsl.dart'; import '../data_class.dart'; @@ -100,6 +101,26 @@ class EnumIndexConverter extends TypeConverter { } } +/// Implementation for an enum to string converter that uses the name of the +/// enum as the value stored in the database. +class EnumNameConverter extends TypeConverter { + /// All values of the enum. + final List values; + + /// Constant default constructor. + const EnumNameConverter(this.values); + + @override + T fromSql(String fromDb) { + return values.byName(fromDb); + } + + @override + String toSql(T value) { + return value.name; + } +} + /// A type converter automatically mapping `null` values to `null` in both /// directions. /// diff --git a/drift_dev/lib/src/analysis/resolver/dart/column.dart b/drift_dev/lib/src/analysis/resolver/dart/column.dart index 4d7b9b9f..75f38124 100644 --- a/drift_dev/lib/src/analysis/resolver/dart/column.dart +++ b/drift_dev/lib/src/analysis/resolver/dart/column.dart @@ -15,7 +15,8 @@ import 'table.dart'; const String _startInt = 'integer'; const String _startInt64 = 'int64'; -const String _startEnum = 'intEnum'; +const String _startIntEnum = 'intEnum'; +const String _startTextEnum = 'textEnum'; const String _startString = 'text'; const String _startBool = 'boolean'; const String _startDateTime = 'dateTime'; @@ -25,7 +26,8 @@ const String _startReal = 'real'; const Set _starters = { _startInt, _startInt64, - _startEnum, + _startIntEnum, + _startTextEnum, _startString, _startBool, _startDateTime, @@ -349,20 +351,37 @@ class ColumnParser { ); } - if (foundStartMethod == _startEnum) { + if (foundStartMethod == _startIntEnum) { if (converter != null) { _resolver.reportError(DriftAnalysisError.forDartElement( element, - 'Using $_startEnum will apply a custom converter by default, ' + 'Using $_startIntEnum will apply a custom converter by default, ' "so you can't add an additional converter", )); } - final enumType = remainingExpr.typeArgumentTypes![0]; + final enumType = remainingExpr.typeArgumentTypes!.first; converter = readEnumConverter( (msg) => _resolver.reportError(DriftAnalysisError.inDartAst(element, remainingExpr.typeArguments ?? remainingExpr.methodName, msg)), enumType, + EnumType.intEnum, + ); + } else if (foundStartMethod == _startTextEnum) { + if (converter != null) { + _resolver.reportError(DriftAnalysisError.forDartElement( + element, + 'Using $_startTextEnum will apply a custom converter by default, ' + "so you can't add an additional converter", + )); + } + + final enumType = remainingExpr.typeArgumentTypes!.first; + converter = readEnumConverter( + (msg) => _resolver.reportError(DriftAnalysisError.inDartAst(element, + remainingExpr.typeArguments ?? remainingExpr.methodName, msg)), + enumType, + EnumType.textEnum, ); } @@ -427,7 +446,8 @@ class ColumnParser { _startString: DriftSqlType.string, _startInt: DriftSqlType.int, _startInt64: DriftSqlType.bigInt, - _startEnum: DriftSqlType.int, + _startIntEnum: DriftSqlType.int, + _startTextEnum: DriftSqlType.string, _startDateTime: DriftSqlType.dateTime, _startBlob: DriftSqlType.blob, _startReal: DriftSqlType.double, diff --git a/drift_dev/lib/src/analysis/resolver/drift/table.dart b/drift_dev/lib/src/analysis/resolver/drift/table.dart index f845d318..2163fbe7 100644 --- a/drift_dev/lib/src/analysis/resolver/drift/table.dart +++ b/drift_dev/lib/src/analysis/resolver/drift/table.dart @@ -54,6 +54,7 @@ class DriftTableResolver extends LocalElementResolver { AnnotatedDartCode? defaultArgument; final typeName = column.definition?.typeName; + final enumMatch = typeName != null ? _enumRegex.firstMatch(typeName) : null; if (enumMatch != null) { @@ -72,6 +73,9 @@ class DriftTableResolver extends LocalElementResolver { (msg) => reportError( DriftAnalysisError.inDriftFile(column.definition ?? stmt, msg)), dartClass.classElement.thisType, + column.type.type == BasicType.int + ? EnumType.intEnum + : EnumType.textEnum, ); } } diff --git a/drift_dev/lib/src/analysis/resolver/shared/dart_types.dart b/drift_dev/lib/src/analysis/resolver/shared/dart_types.dart index c6fd9f71..f5a2a60b 100644 --- a/drift_dev/lib/src/analysis/resolver/shared/dart_types.dart +++ b/drift_dev/lib/src/analysis/resolver/shared/dart_types.dart @@ -160,6 +160,11 @@ ExistingRowClass? validateExistingClass( ); } +enum EnumType { + intEnum, + textEnum, +} + AppliedTypeConverter? readTypeConverter( LibraryElement library, Expression dartExpression, @@ -223,32 +228,39 @@ AppliedTypeConverter? readTypeConverter( AppliedTypeConverter readEnumConverter( void Function(String) reportError, - DartType enumType, + DartType dartEnumType, + EnumType columnEnumType, ) { - if (enumType is! InterfaceType) { - reportError('Not a class: `$enumType`'); + if (dartEnumType is! InterfaceType) { + reportError('Not a class: `$dartEnumType`'); } - final creatingClass = enumType.element; + final creatingClass = dartEnumType.element; if (creatingClass is! EnumElement) { reportError('Not an enum: `${creatingClass!.displayName}`'); } // `const EnumIndexConverter(EnumType.values)` + // or + // `const EnumNameConverter(EnumType.values)` final expression = AnnotatedDartCode.build((builder) { + builder.addText('const '); + if (columnEnumType == EnumType.intEnum) { + builder.addSymbol('EnumIndexConverter', AnnotatedDartCode.drift); + } else { + builder.addSymbol('EnumNameConverter', AnnotatedDartCode.drift); + } builder - ..addText('const ') - ..addSymbol('EnumIndexConverter', AnnotatedDartCode.drift) ..addText('<') - ..addDartType(enumType) + ..addDartType(dartEnumType) ..addText('>(') - ..addDartType(enumType) + ..addDartType(dartEnumType) ..addText('.values)'); }); return AppliedTypeConverter( expression: expression, - dartType: enumType, + dartType: dartEnumType, jsonType: null, sqlType: DriftSqlType.int, dartTypeIsNullable: false,