From 96c2e8641f6f423316b78c9b3c86990de19e20dd Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 9 Feb 2019 15:17:37 +0100 Subject: [PATCH] Prepare code for table / column creation --- example/lib/example.g.dart | 16 ++--- sally/lib/src/runtime/executor/executor.dart | 10 ++-- sally/lib/src/runtime/migration.dart | 58 +++++++++++++++---- sally/lib/src/runtime/structure/columns.dart | 31 ++++++++++ .../lib/src/runtime/structure/table_info.dart | 6 +- sally/test/generated_tables.dart | 10 ++-- sally_flutter/lib/sally_flutter.dart | 36 ++---------- .../lib/src/model/specified_column.dart | 16 ++--- .../lib/src/model/specified_database.dart | 2 - .../lib/src/model/specified_table.dart | 1 + .../lib/src/parser/table_parser.dart | 3 +- sally_generator/lib/src/sally_generator.dart | 6 +- sally_generator/lib/src/utils/names.dart | 2 +- .../lib/src/writer/database_writer.dart | 8 +-- .../lib/src/writer/table_writer.dart | 27 +++++---- 15 files changed, 140 insertions(+), 92 deletions(-) diff --git a/example/lib/example.g.dart b/example/lib/example.g.dart index e789f63a..5e11032a 100644 --- a/example/lib/example.g.dart +++ b/example/lib/example.g.dart @@ -16,17 +16,17 @@ class _$ProductsTable extends Products implements TableInfo { 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 get $columns => [id, name]; + List get $columns => [id, name]; @override Products get asDslTable => this; @override String get $tableName => 'products'; @override - Set get $primaryKey => Set(); + Set get $primaryKey => Set(); @override Product map(Map data) { final intType = db.typeSystem.forDartType(); @@ -48,17 +48,17 @@ class _$UsersTable extends Users implements TableInfo { 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 get $columns => [id, name]; + List get $columns => [id, name]; @override Users get asDslTable => this; @override String get $tableName => 'users'; @override - Set get $primaryKey => Set(); + Set get $primaryKey => Set(); @override User map(Map data) { final intType = db.typeSystem.forDartType(); diff --git a/sally/lib/src/runtime/executor/executor.dart b/sally/lib/src/runtime/executor/executor.dart index 8cc47c0a..d068a14a 100644 --- a/sally/lib/src/runtime/executor/executor.dart +++ b/sally/lib/src/runtime/executor/executor.dart @@ -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 handleDatabaseCreation(QueryExecutor executor) { + Future handleDatabaseCreation({@required SqlExecutor executor}) { final migrator = _createMigrator(executor); return migration.onCreate(migrator); } - Future handleDatabaseVersionChange(QueryExecutor executor, int from, int to) { + Future 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 ensureOpen(); @@ -50,4 +51,5 @@ abstract class QueryExecutor { Future runInsert(String statement, List args); Future runUpdate(String statement, List args); Future runDelete(String statement, List args); + Future runCustom(String statement); } diff --git a/sally/lib/src/runtime/migration.dart b/sally/lib/src/runtime/migration.dart index 25ddd4b1..dbb34213 100644 --- a/sally/lib/src/runtime/migration.dart +++ b/sally/lib/src/runtime/migration.dart @@ -8,8 +8,8 @@ typedef Future OnUpgrade(Migrator m, int from, int to); Future _defaultOnCreate(Migrator m) => m.createAllTables(); Future _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 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 createAllTables() async {} + Future createAllTables() async { + return Future.wait(_db.allTables.map(createTable)); + } - Future createTable(TableInfo table) async {} + Future createTable(TableInfo table) async { + final sql = StringBuffer(); - Future deleteTable(String name) async {} + // todo write primary key - Future addColumn(TableInfo table, GeneratedColumn column) async {} + // ignore: cascade_invocations + sql.write('CREATE TABLE IF NOT EXISTS ${table.$tableName} ('); - Future deleteColumn(TableInfo table, String columnName) async {} + for (var i = 0; i < table.$columns.length; i++) { + final column = table.$columns[i]; - Future 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 deleteTable(String name) async { + return issueCustomQuery('DROP TABLE IF EXISTS $name'); + } + + Future 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 issueCustomQuery(String sql) async { + return _executor(sql); + } } diff --git a/sally/lib/src/runtime/structure/columns.dart b/sally/lib/src/runtime/structure/columns.dart index a2915c7a..8cc83c55 100644 --- a/sally/lib/src/runtime/structure/columns.dart +++ b/sally/lib/src/runtime/structure/columns.dart @@ -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> extends Column { 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 equals(Expression compare) => Comparison.equal(this, compare); @@ -31,12 +48,23 @@ class GeneratedTextColumn extends GeneratedColumn @override Expression like(String regex) => LikeOperator(this, Variable(regex)); + + @override + final String typeName = 'VARCHAR'; } class GeneratedBoolColumn extends GeneratedColumn 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 implements IntColumn { final bool hasAutoIncrement; + @override + final String typeName = 'INTEGER'; + GeneratedIntColumn(String name, bool nullable, {this.hasAutoIncrement = false}) : super(name, nullable); diff --git a/sally/lib/src/runtime/structure/table_info.dart b/sally/lib/src/runtime/structure/table_info.dart index 82014a6d..2b11b480 100644 --- a/sally/lib/src/runtime/structure/table_info.dart +++ b/sally/lib/src/runtime/structure/table_info.dart @@ -6,11 +6,11 @@ abstract class TableInfo { /// The primary key of this table. Can be null if no custom primary key has /// been specified - Set get $primaryKey => null; + Set get $primaryKey => null; /// The table name in the sql table String get $tableName; - List get $columns; + List get $columns; DataClass map(Map data); -} \ No newline at end of file +} diff --git a/sally/test/generated_tables.dart b/sally/test/generated_tables.dart index af4cb62b..0bc832e5 100644 --- a/sally/test/generated_tables.dart +++ b/sally/test/generated_tables.dart @@ -21,15 +21,15 @@ class GeneratedUsersTable extends Users with TableInfo { GeneratedUsersTable(this.db); @override - Set get $primaryKey => Set()..add(id); + Set 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> get $columns => [id, name, isAwesome]; + List> get $columns => [id, name, isAwesome]; @override String get $tableName => 'users'; @override diff --git a/sally_flutter/lib/sally_flutter.dart b/sally_flutter/lib/sally_flutter.dart index f5ca3e84..577e9b19 100644 --- a/sally_flutter/lib/sally_flutter.dart +++ b/sally_flutter/lib/sally_flutter.dart @@ -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 runUpdate(String statement, List args) { return _db.rawUpdate(statement, args); } -} - -class _SqfliteExecutor extends QueryExecutor { - final Database _db; - - _SqfliteExecutor(this._db); @override - Future ensureOpen() async { - return true; - } - - @override - Future runDelete(String statement, List args) { - return _db.rawDelete(statement, args); - } - - @override - Future runInsert(String statement, List args) { - return _db.rawInsert(statement, args); - } - - @override - Future>> runSelect(String statement, List args) { - return _db.rawQuery(statement, args); - } - - @override - Future runUpdate(String statement, List args) { - return _db.rawUpdate(statement, args); + Future runCustom(String statement) { + return _db.execute(statement); } } diff --git a/sally_generator/lib/src/model/specified_column.dart b/sally_generator/lib/src/model/specified_column.dart index 4fa1539a..c6f480af 100644 --- a/sally_generator/lib/src/model/specified_column.dart +++ b/sally_generator/lib/src/model/specified_column.dart @@ -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, diff --git a/sally_generator/lib/src/model/specified_database.dart b/sally_generator/lib/src/model/specified_database.dart index 209f85e7..bb841e8b 100644 --- a/sally_generator/lib/src/model/specified_database.dart +++ b/sally_generator/lib/src/model/specified_database.dart @@ -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 tables; SpecifiedDatabase(this.fromClass, this.tables); - } diff --git a/sally_generator/lib/src/model/specified_table.dart b/sally_generator/lib/src/model/specified_table.dart index e0772fe4..eda14b67 100644 --- a/sally_generator/lib/src/model/specified_table.dart +++ b/sally_generator/lib/src/model/specified_table.dart @@ -5,6 +5,7 @@ class SpecifiedTable { final ClassElement fromClass; final List columns; final String sqlName; + /// The name for the data class associated with this table final String dartTypeName; diff --git a/sally_generator/lib/src/parser/table_parser.dart b/sally_generator/lib/src/parser/table_parser.dart index bafd24a5..41184076 100644 --- a/sally_generator/lib/src/parser/table_parser.dart +++ b/sally_generator/lib/src/parser/table_parser.dart @@ -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) { diff --git a/sally_generator/lib/src/sally_generator.dart b/sally_generator/lib/src/sally_generator.dart index b8db3219..2177a9ec 100644 --- a/sally_generator/lib/src/sally_generator.dart +++ b/sally_generator/lib/src/sally_generator.dart @@ -56,10 +56,10 @@ class SallyGenerator extends GeneratorForAnnotation { } } - 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); diff --git a/sally_generator/lib/src/utils/names.dart b/sally_generator/lib/src/utils/names.dart index 3dd7e7ad..7fa82c52 100644 --- a/sally_generator/lib/src/utils/names.dart +++ b/sally_generator/lib/src/utils/names.dart @@ -13,4 +13,4 @@ String dataClassNameForClassName(String tableName) { // Default behavior if the table name is not a valid plural. return '${tableName}Data'; -} \ No newline at end of file +} diff --git a/sally_generator/lib/src/writer/database_writer.dart b/sally_generator/lib/src/writer/database_writer.dart index d982c809..ce729e54 100644 --- a/sally_generator/lib/src/writer/database_writer.dart +++ b/sally_generator/lib/src/writer/database_writer.dart @@ -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 = []; @@ -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}'); } -} \ No newline at end of file +} diff --git a/sally_generator/lib/src/writer/table_writer.dart b/sally_generator/lib/src/writer/table_writer.dart index b8ff173d..e8eec86b 100644 --- a/sally_generator/lib/src/writer/table_writer.dart +++ b/sally_generator/lib/src/writer/table_writer.dart @@ -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 get \$columns => [$columnsWithGetters];\n') - ..write('@override\n$tableDslName get asDslTable => this;\n') - ..write('@override\nString get \$tableName => \'${table.sqlName}\';\n'); + ..write( + '@override\nList 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 get $primaryKey => Set().add(id); final primaryKeyColumns = table.primaryKey.map((c) => c.dartGetterName); - buffer.write('@override\nSet get \$primaryKey => Set()'); + buffer.write('@override\nSet 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 data) {\n'); + buffer + .write('@override\n$dataClassName map(Map data) {\n'); final dartTypeToResolver = {}; @@ -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,'); }