Validate data integrity before inserts and updates

This commit is contained in:
Simon Binder 2019-02-13 19:30:13 +01:00
parent 81b0bac19b
commit c9aab2e824
4 changed files with 89 additions and 23 deletions

View File

@ -24,14 +24,21 @@ class User {
}
class _$UsersTable extends Users implements TableInfo<Users, User> {
final GeneratedDatabase db;
_$UsersTable(this.db);
final GeneratedDatabase _db;
_$UsersTable(this._db);
@override
GeneratedIntColumn get id => GeneratedIntColumn('id', false);
GeneratedIntColumn get id =>
GeneratedIntColumn('id', false, hasAutoIncrement: true);
@override
GeneratedTextColumn get userName => GeneratedTextColumn('name', false);
GeneratedTextColumn get userName => GeneratedTextColumn(
'name',
false,
);
@override
GeneratedTextColumn get bio => GeneratedTextColumn('bio', false);
GeneratedTextColumn get bio => GeneratedTextColumn(
'bio',
false,
);
@override
List<GeneratedColumn> get $columns => [id, userName, bio];
@override
@ -39,13 +46,16 @@ class _$UsersTable extends Users implements TableInfo<Users, User> {
@override
String get $tableName => 'users';
@override
void validateIntegrity(User instance, bool isInserting) => null;
void validateIntegrity(User instance, bool isInserting) =>
id.isAcceptableValue(instance.id, isInserting) &&
userName.isAcceptableValue(instance.userName, isInserting) &&
bio.isAcceptableValue(instance.bio, isInserting);
@override
Set<GeneratedColumn> get $primaryKey => Set();
@override
User map(Map<String, dynamic> data) {
final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>();
final intType = _db.typeSystem.forDartType<int>();
final stringType = _db.typeSystem.forDartType<String>();
return User(
id: intType.mapFromDatabaseResponse(data['id']),
userName: stringType.mapFromDatabaseResponse(data['name']),

View File

@ -80,7 +80,7 @@ class MyDatabase extends _$MyDatabase {
MigrationStrategy get migration => MigrationStrategy();
}
```
You can ignore these two getters there at the moment, the imporant part is that you can
You can ignore these two getters there at the moment, the important part is that you can
now run your queries with fluent Dart code:
```dart
class MyDatabase extends _$MyDatabase {
@ -114,6 +114,7 @@ supporting floating / fixed point numbers as well would be awesome
- DSL API
- Support in generator
- Use in queries (`IS NOT NULL`)
- Setting fields to null during updates
- Verify constraints (text length, nullability, etc.) before inserting or
deleting data.
- Support Dart VM apps

View File

@ -39,6 +39,17 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
@override
Expression<BoolType> equals(T compare) => equalsExp(Variable<T, S>(compare));
/// Checks whether the given value fits into this column. The default
/// implementation checks whether the value is not null, as null values are
/// only allowed for updates or if the column is nullable.
/// If [duringInsert] is true, the method should check whether the value is
/// suitable for a new row that is being inserted. If it's false, we the
/// method should check whether the value is valid for an update. Null values
/// should always be accepted for updates, as the describe a value that should
/// not be replaced.
bool isAcceptableValue(T value, bool duringInsert) =>
($nullable || !duringInsert) || value != null;
}
class GeneratedTextColumn extends GeneratedColumn<String, StringType>
@ -91,4 +102,8 @@ class GeneratedIntColumn extends GeneratedColumn<int, IntType>
@override
Expression<BoolType> isSmallerThan(int i) =>
Comparison(this, ComparisonOperator.less, Variable<int, IntType>(i));
@override
bool isAcceptableValue(int value, bool duringInsert) =>
hasAutoIncrement || super.isAcceptableValue(value, duringInsert);
}

View File

@ -1,4 +1,5 @@
import 'package:recase/recase.dart';
import 'package:sally_generator/src/model/specified_column.dart';
import 'package:sally_generator/src/model/specified_table.dart';
import 'package:sally_generator/src/writer/data_class_writer.dart';
@ -25,19 +26,12 @@ class TableWriter {
..write('class ${table.tableInfoName} extends $tableDslName '
'implements TableInfo<$tableDslName, $dataClass> {\n')
// should have a GeneratedDatabase reference that is set in the constructor
..write('final GeneratedDatabase db;\n')
..write('${table.tableInfoName}(this.db);\n');
..write('final GeneratedDatabase _db;\n')
..write('${table.tableInfoName}(this._db);\n');
// Generate the columns
for (var column in table.columns) {
final isNullable = false;
// @override
// GeneratedIntColumn get id => GeneratedIntColumn('sql_name', isNullable);
buffer
..write('@override \n')
..write('${column.implColumnTypeName} get ${column.dartGetterName} => '
'${column.implColumnTypeName}(\'${column.name.name}\', $isNullable);\n');
_writeColumnGetter(buffer, column);
}
// Generate $columns, $tableName, asDslTable getters
@ -48,9 +42,9 @@ class TableWriter {
..write(
'@override\nList<GeneratedColumn> get \$columns => [$columnsWithGetters];\n')
..write('@override\n$tableDslName get asDslTable => this;\n')
..write('@override\nString get \$tableName => \'${table.sqlName}\';\n')
..write(
'@override\nvoid validateIntegrity($dataClass instance, bool isInserting) => null;');
..write('@override\nString get \$tableName => \'${table.sqlName}\';\n');
_writeValidityCheckMethod(buffer);
// todo replace set syntax with literal once dart supports it
// write primary key getter: Set<Column> get $primaryKey => Set().add(id);
@ -83,7 +77,7 @@ class TableWriter {
dartTypeToResolver[usedType] = resolver;
buffer
.write('final $resolver = db.typeSystem.forDartType<$usedType>();\n');
.write('final $resolver = _db.typeSystem.forDartType<$usedType>();\n');
}
// finally, the mighty constructor invocation:
@ -119,4 +113,50 @@ class TableWriter {
buffer.write('return map; \n}\n');
}
void _writeColumnGetter(StringBuffer buffer, SpecifiedColumn column) {
final isNullable = false; // todo nullability for columns
final additionalParams = <String, String>{};
if (column.hasAI) {
additionalParams['hasAutoIncrement'] = 'true';
}
// @override
// GeneratedIntColumn get id => GeneratedIntColumn('sql_name', isNullable);
buffer
..write('@override \n')
..write('${column.implColumnTypeName} get ${column.dartGetterName} => '
'${column.implColumnTypeName}(\'${column.name.name}\', $isNullable, ');
var first = true;
additionalParams.forEach((name, value) {
if (!first) {
buffer.write(', ');
} else {
first = false;
}
buffer..write(name)..write(': ')..write(value);
});
buffer.write(');\n');
}
void _writeValidityCheckMethod(StringBuffer buffer) {
final dataClass = table.dartTypeName;
buffer.write('@override\nvoid validateIntegrity($dataClass instance, bool isInserting) => ');
final validationCode = table.columns.map((column) {
final getterName = column.dartGetterName;
// generated columns have a isAcceptableValue(T value, bool duringInsert)
// method
return '$getterName.isAcceptableValue(instance.$getterName, isInserting)';
}).join('&&');
buffer..write(validationCode)..write(';\n');
}
}