Prepare code for table / column creation

This commit is contained in:
Simon Binder 2019-02-09 15:17:37 +01:00
parent 8a14b4e8d0
commit 96c2e8641f
15 changed files with 140 additions and 92 deletions

View File

@ -16,17 +16,17 @@ class _$ProductsTable extends Products implements TableInfo<Products, Product> {
final GeneratedDatabase db;
_$ProductsTable(this.db);
@override
IntColumn get id => GeneratedIntColumn('products_id', false);
GeneratedIntColumn get id => GeneratedIntColumn('products_id', false);
@override
TextColumn get name => GeneratedTextColumn('name', false);
GeneratedTextColumn get name => GeneratedTextColumn('name', false);
@override
List<Column> get $columns => [id, name];
List<GeneratedColumn> get $columns => [id, name];
@override
Products get asDslTable => this;
@override
String get $tableName => 'products';
@override
Set<Column> get $primaryKey => Set();
Set<GeneratedColumn> get $primaryKey => Set();
@override
Product map(Map<String, dynamic> data) {
final intType = db.typeSystem.forDartType<int>();
@ -48,17 +48,17 @@ class _$UsersTable extends Users implements TableInfo<Users, User> {
final GeneratedDatabase db;
_$UsersTable(this.db);
@override
IntColumn get id => GeneratedIntColumn('id', false);
GeneratedIntColumn get id => GeneratedIntColumn('id', false);
@override
TextColumn get name => GeneratedTextColumn('name', false);
GeneratedTextColumn get name => GeneratedTextColumn('name', false);
@override
List<Column> get $columns => [id, name];
List<GeneratedColumn> get $columns => [id, name];
@override
Users get asDslTable => this;
@override
String get $tableName => 'users';
@override
Set<Column> get $primaryKey => Set();
Set<GeneratedColumn> get $primaryKey => Set();
@override
User map(Map<String, dynamic> data) {
final intType = db.typeSystem.forDartType<int>();

View File

@ -1,3 +1,4 @@
import 'package:meta/meta.dart';
import 'package:sally/sally.dart';
import 'package:sally/src/runtime/executor/type_system.dart';
import 'package:sally/src/runtime/migration.dart';
@ -19,14 +20,15 @@ abstract class GeneratedDatabase {
/// Creates a migrator with the provided query executor. We sometimes can't
/// use the regular [GeneratedDatabase.executor] because migration happens
/// before that executor is ready.
Migrator _createMigrator(QueryExecutor executor) => Migrator(this, executor);
Migrator _createMigrator(SqlExecutor executor) => Migrator(this, executor);
Future<void> handleDatabaseCreation(QueryExecutor executor) {
Future<void> handleDatabaseCreation({@required SqlExecutor executor}) {
final migrator = _createMigrator(executor);
return migration.onCreate(migrator);
}
Future<void> handleDatabaseVersionChange(QueryExecutor executor, int from, int to) {
Future<void> handleDatabaseVersionChange(
{@required SqlExecutor executor, int from, int to}) {
final migrator = _createMigrator(executor);
return migration.onUpgrade(migrator, from, to);
}
@ -41,7 +43,6 @@ abstract class GeneratedDatabase {
}
abstract class QueryExecutor {
GeneratedDatabase databaseInfo;
Future<bool> ensureOpen();
@ -50,4 +51,5 @@ abstract class QueryExecutor {
Future<int> runInsert(String statement, List<dynamic> args);
Future<int> runUpdate(String statement, List<dynamic> args);
Future<int> runDelete(String statement, List<dynamic> args);
Future<void> runCustom(String statement);
}

View File

@ -8,8 +8,8 @@ typedef Future<void> OnUpgrade(Migrator m, int from, int to);
Future<void> _defaultOnCreate(Migrator m) => m.createAllTables();
Future<void> _defaultOnUpdate(Migrator m, int from, int to) async =>
throw Exception("You've bumped the schema version for your sally database "
"but didn't provide a strategy for schema updates. Please do that by "
'adapting the migrations getter in your database class.');
"but didn't provide a strategy for schema updates. Please do that by "
'adapting the migrations getter in your database class.');
class MigrationStrategy {
final OnCreate onCreate;
@ -21,22 +21,58 @@ class MigrationStrategy {
});
}
/// A function that executes queries and ignores what they return.
typedef Future<void> SqlExecutor(String sql);
class Migrator {
final GeneratedDatabase _db;
final QueryExecutor _customExecutor;
final SqlExecutor _executor;
Migrator(this._db, this._customExecutor);
Migrator(this._db, this._executor);
Future<void> createAllTables() async {}
Future<void> createAllTables() async {
return Future.wait(_db.allTables.map(createTable));
}
Future<void> createTable(TableInfo table) async {}
Future<void> createTable(TableInfo table) async {
final sql = StringBuffer();
Future<void> deleteTable(String name) async {}
// todo write primary key
Future<void> addColumn(TableInfo table, GeneratedColumn column) async {}
// ignore: cascade_invocations
sql.write('CREATE TABLE IF NOT EXISTS ${table.$tableName} (');
Future<void> deleteColumn(TableInfo table, String columnName) async {}
for (var i = 0; i < table.$columns.length; i++) {
final column = table.$columns[i];
Future<void> issueCustomQuery(String sql) async {}
// ignore: cascade_invocations
column.writeColumnDefinition(sql);
if (i < table.$columns.length - 1) sql.write(', ');
}
sql.write(')');
return issueCustomQuery(sql.toString());
}
/// Deletes the table with the given name. Note that this function does not
/// escape the [name] parameter.
Future<void> deleteTable(String name) async {
return issueCustomQuery('DROP TABLE IF EXISTS $name');
}
Future<void> addColumn(TableInfo table, GeneratedColumn column) async {
final sql = StringBuffer();
// ignore: cascade_invocations
sql.write('ALTER TABLE ${table.$tableName} ADD COLUMN');
column.writeColumnDefinition(sql);
return issueCustomQuery(sql.toString());
}
Future<void> issueCustomQuery(String sql) async {
return _executor(sql);
}
}

View File

@ -1,3 +1,4 @@
import 'package:meta/meta.dart';
import 'package:sally/sally.dart';
import 'package:sally/src/runtime/components/component.dart';
import 'package:sally/src/runtime/expressions/expression.dart';
@ -11,6 +12,22 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
GeneratedColumn(this.$name, this.$nullable);
/// Writes the definition of this column, as defined
/// [here](https://www.sqlite.org/syntax/column-def.html), into the given
/// buffer.
void writeColumnDefinition(StringBuffer into) {
into
..write('${$name} $typeName ')
..write($nullable ? 'NULL' : 'NOT NULL')
..write(' ');
writeCustomConstraints(into);
}
@visibleForOverriding
void writeCustomConstraints(StringBuffer into) {}
@visibleForOverriding
String get typeName;
@override
Expression<BoolType> equals(Expression<S> compare) =>
Comparison.equal(this, compare);
@ -31,12 +48,23 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
@override
Expression<BoolType> like(String regex) =>
LikeOperator(this, Variable<String, StringType>(regex));
@override
final String typeName = 'VARCHAR';
}
class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
implements BoolColumn {
GeneratedBoolColumn(String name, bool nullable) : super(name, nullable);
@override
final String typeName = 'BOOLEAN';
@override
void writeCustomConstraints(StringBuffer into) {
into.write('CHECK (${$name} in (0, 1))');
}
@override
void writeInto(GenerationContext context) {
context.buffer.write('(');
@ -49,6 +77,9 @@ class GeneratedIntColumn extends GeneratedColumn<int, IntType>
implements IntColumn {
final bool hasAutoIncrement;
@override
final String typeName = 'INTEGER';
GeneratedIntColumn(String name, bool nullable,
{this.hasAutoIncrement = false})
: super(name, nullable);

View File

@ -6,11 +6,11 @@ abstract class TableInfo<TableDsl, DataClass> {
/// The primary key of this table. Can be null if no custom primary key has
/// been specified
Set<Column> get $primaryKey => null;
Set<GeneratedColumn> get $primaryKey => null;
/// The table name in the sql table
String get $tableName;
List<Column> get $columns;
List<GeneratedColumn> get $columns;
DataClass map(Map<String, dynamic> data);
}
}

View File

@ -21,15 +21,15 @@ class GeneratedUsersTable extends Users with TableInfo<Users, UserDataObject> {
GeneratedUsersTable(this.db);
@override
Set<Column> get $primaryKey => Set()..add(id);
Set<GeneratedColumn> get $primaryKey => Set()..add(id);
@override
IntColumn id = GeneratedIntColumn('id', false);
GeneratedIntColumn id = GeneratedIntColumn('id', false);
@override
TextColumn name = GeneratedTextColumn('name', false);
GeneratedTextColumn name = GeneratedTextColumn('name', false);
@override
BoolColumn isAwesome = GeneratedBoolColumn('is_awesome', true);
GeneratedBoolColumn isAwesome = GeneratedBoolColumn('is_awesome', true);
@override
List<Column<dynamic, SqlType>> get $columns => [id, name, isAwesome];
List<GeneratedColumn<dynamic, SqlType>> get $columns => [id, name, isAwesome];
@override
String get $tableName => 'users';
@override

View File

@ -36,11 +36,13 @@ class FlutterQueryExecutor extends QueryExecutor {
resolvedPath,
version: databaseInfo.schemaVersion,
onCreate: (db, version) {
return databaseInfo.handleDatabaseCreation(_SqfliteExecutor(db));
return databaseInfo.handleDatabaseCreation(
executor: (sql) => db.execute(sql),
);
},
onUpgrade: (db, from, to) {
return databaseInfo.handleDatabaseVersionChange(
_SqfliteExecutor(db), from, to);
executor: (sql) => db.execute(sql), from: from, to: to);
},
);
@ -66,35 +68,9 @@ class FlutterQueryExecutor extends QueryExecutor {
Future<int> runUpdate(String statement, List args) {
return _db.rawUpdate(statement, args);
}
}
class _SqfliteExecutor extends QueryExecutor {
final Database _db;
_SqfliteExecutor(this._db);
@override
Future<bool> ensureOpen() async {
return true;
}
@override
Future<int> runDelete(String statement, List args) {
return _db.rawDelete(statement, args);
}
@override
Future<int> runInsert(String statement, List args) {
return _db.rawInsert(statement, args);
}
@override
Future<List<Map<String, dynamic>>> runSelect(String statement, List args) {
return _db.rawQuery(statement, args);
}
@override
Future<int> runUpdate(String statement, List args) {
return _db.rawUpdate(statement, args);
Future<void> runCustom(String statement) {
return _db.execute(statement);
}
}

View File

@ -45,16 +45,16 @@ class SpecifiedColumn {
}[type];
String get dslColumnTypeName => {
ColumnType.boolean: 'BoolColumn',
ColumnType.text: 'TextColumn',
ColumnType.integer: 'IntColumn'
}[type];
ColumnType.boolean: 'BoolColumn',
ColumnType.text: 'TextColumn',
ColumnType.integer: 'IntColumn'
}[type];
String get implColumnTypeName => {
ColumnType.boolean: 'GeneratedBoolColumn',
ColumnType.text: 'GeneratedTextColumn',
ColumnType.integer: 'GeneratedIntColumn'
}[type];
ColumnType.boolean: 'GeneratedBoolColumn',
ColumnType.text: 'GeneratedTextColumn',
ColumnType.integer: 'GeneratedIntColumn'
}[type];
const SpecifiedColumn(
{this.type,

View File

@ -2,10 +2,8 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:sally_generator/src/model/specified_table.dart';
class SpecifiedDatabase {
final ClassElement fromClass;
final List<SpecifiedTable> tables;
SpecifiedDatabase(this.fromClass, this.tables);
}

View File

@ -5,6 +5,7 @@ class SpecifiedTable {
final ClassElement fromClass;
final List<SpecifiedColumn> columns;
final String sqlName;
/// The name for the data class associated with this table
final String dartTypeName;

View File

@ -19,8 +19,7 @@ class TableParser extends ParserBase {
fromClass: element,
columns: _parseColumns(element),
sqlName: sqlName,
dartTypeName: dataClassNameForClassName(element.name)
);
dartTypeName: dataClassNameForClassName(element.name));
}
String _parseTableName(ClassElement element) {

View File

@ -56,10 +56,10 @@ class SallyGenerator extends GeneratorForAnnotation<UseSally> {
}
}
if (_foundTables.isEmpty)
return '';
if (_foundTables.isEmpty) return '';
final specifiedDb = SpecifiedDatabase(element as ClassElement, tablesForThisDb);
final specifiedDb =
SpecifiedDatabase(element as ClassElement, tablesForThisDb);
final buffer = StringBuffer();
DatabaseWriter(specifiedDb).write(buffer);

View File

@ -13,4 +13,4 @@ String dataClassNameForClassName(String tableName) {
// Default behavior if the table name is not a valid plural.
return '${tableName}Data';
}
}

View File

@ -3,7 +3,6 @@ import 'package:sally_generator/src/model/specified_database.dart';
import 'package:sally_generator/src/writer/table_writer.dart';
class DatabaseWriter {
final SpecifiedDatabase db;
DatabaseWriter(this.db);
@ -17,7 +16,7 @@ class DatabaseWriter {
// Write the database class
final className = '_\$${db.fromClass.name}';
buffer.write('abstract class $className extends GeneratedDatabase {\n'
'$className() : super(const SqlTypeSystem.withDefaults(), null); \n');
'$className() : super(const SqlTypeSystem.withDefaults(), null); \n');
final tableGetters = <String>[];
@ -26,7 +25,8 @@ class DatabaseWriter {
tableGetters.add(tableFieldName);
final tableClassName = table.tableInfoName;
buffer.write('$tableClassName get $tableFieldName => $tableClassName(this);');
buffer.write(
'$tableClassName get $tableFieldName => $tableClassName(this);');
}
// Write List of tables, close bracket for class
@ -35,4 +35,4 @@ class DatabaseWriter {
..write(tableGetters.join(','))
..write('];\n}');
}
}
}

View File

@ -47,25 +47,27 @@ class TableWriter {
final isNullable = false;
// @override
// IntColumn get id => GeneratedIntColumn('sql_name', isNullable);
// GeneratedIntColumn get id => GeneratedIntColumn('sql_name', isNullable);
buffer
..write('@override \n')
..write('${column.dslColumnTypeName} get ${column.dartGetterName} => '
'${column.implColumnTypeName}(\'${column.name.name}\', $isNullable);\n');
..write('${column.implColumnTypeName} get ${column.dartGetterName} => '
'${column.implColumnTypeName}(\'${column.name.name}\', $isNullable);\n');
}
// Generate $columns, $tableName, asDslTable getters
final columnsWithGetters = table.columns.map((c) => c.dartGetterName).join(', ');
final columnsWithGetters =
table.columns.map((c) => c.dartGetterName).join(', ');
buffer
..write('@override\nList<Column> get \$columns => [$columnsWithGetters];\n')
..write('@override\n$tableDslName get asDslTable => this;\n')
..write('@override\nString get \$tableName => \'${table.sqlName}\';\n');
..write(
'@override\nList<GeneratedColumn> get \$columns => [$columnsWithGetters];\n')
..write('@override\n$tableDslName get asDslTable => this;\n')
..write('@override\nString get \$tableName => \'${table.sqlName}\';\n');
// todo replace set syntax with literal once dart supports it
// write primary key getter: Set<Column> get $primaryKey => Set().add(id);
final primaryKeyColumns = table.primaryKey.map((c) => c.dartGetterName);
buffer.write('@override\nSet<Column> get \$primaryKey => Set()');
buffer.write('@override\nSet<GeneratedColumn> get \$primaryKey => Set()');
for (var pkColumn in primaryKeyColumns) {
buffer.write('..add($pkColumn)');
}
@ -80,7 +82,8 @@ class TableWriter {
void _writeMappingMethod(StringBuffer buffer) {
final dataClassName = table.dartTypeName;
buffer.write('@override\n$dataClassName map(Map<String, dynamic> data) {\n');
buffer
.write('@override\n$dataClassName map(Map<String, dynamic> data) {\n');
final dartTypeToResolver = <String, String>{};
@ -90,7 +93,8 @@ class TableWriter {
final resolver = '${ReCase(usedType).camelCase}Type';
dartTypeToResolver[usedType] = resolver;
buffer.write('final $resolver = db.typeSystem.forDartType<$usedType>();\n');
buffer
.write('final $resolver = db.typeSystem.forDartType<$usedType>();\n');
}
// finally, the mighty constructor invocation:
@ -100,7 +104,8 @@ class TableWriter {
// id: intType.mapFromDatabaseResponse(data["id])
final getter = column.dartGetterName;
final resolver = dartTypeToResolver[column.dartTypeName];
final typeParser = '$resolver.mapFromDatabaseResponse(data[\'${column.name.name}\'])';
final typeParser =
'$resolver.mapFromDatabaseResponse(data[\'${column.name.name}\'])';
buffer.write('$getter: $typeParser,');
}