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; final GeneratedDatabase db;
_$ProductsTable(this.db); _$ProductsTable(this.db);
@override @override
IntColumn get id => GeneratedIntColumn('products_id', false); GeneratedIntColumn get id => GeneratedIntColumn('products_id', false);
@override @override
TextColumn get name => GeneratedTextColumn('name', false); GeneratedTextColumn get name => GeneratedTextColumn('name', false);
@override @override
List<Column> get $columns => [id, name]; List<GeneratedColumn> get $columns => [id, name];
@override @override
Products get asDslTable => this; Products get asDslTable => this;
@override @override
String get $tableName => 'products'; String get $tableName => 'products';
@override @override
Set<Column> get $primaryKey => Set(); Set<GeneratedColumn> get $primaryKey => Set();
@override @override
Product map(Map<String, dynamic> data) { Product map(Map<String, dynamic> data) {
final intType = db.typeSystem.forDartType<int>(); final intType = db.typeSystem.forDartType<int>();
@ -48,17 +48,17 @@ class _$UsersTable extends Users implements TableInfo<Users, User> {
final GeneratedDatabase db; final GeneratedDatabase db;
_$UsersTable(this.db); _$UsersTable(this.db);
@override @override
IntColumn get id => GeneratedIntColumn('id', false); GeneratedIntColumn get id => GeneratedIntColumn('id', false);
@override @override
TextColumn get name => GeneratedTextColumn('name', false); GeneratedTextColumn get name => GeneratedTextColumn('name', false);
@override @override
List<Column> get $columns => [id, name]; List<GeneratedColumn> get $columns => [id, name];
@override @override
Users get asDslTable => this; Users get asDslTable => this;
@override @override
String get $tableName => 'users'; String get $tableName => 'users';
@override @override
Set<Column> get $primaryKey => Set(); Set<GeneratedColumn> get $primaryKey => Set();
@override @override
User map(Map<String, dynamic> data) { User map(Map<String, dynamic> data) {
final intType = db.typeSystem.forDartType<int>(); 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/sally.dart';
import 'package:sally/src/runtime/executor/type_system.dart'; import 'package:sally/src/runtime/executor/type_system.dart';
import 'package:sally/src/runtime/migration.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 /// Creates a migrator with the provided query executor. We sometimes can't
/// use the regular [GeneratedDatabase.executor] because migration happens /// use the regular [GeneratedDatabase.executor] because migration happens
/// before that executor is ready. /// 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); final migrator = _createMigrator(executor);
return migration.onCreate(migrator); 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); final migrator = _createMigrator(executor);
return migration.onUpgrade(migrator, from, to); return migration.onUpgrade(migrator, from, to);
} }
@ -41,7 +43,6 @@ abstract class GeneratedDatabase {
} }
abstract class QueryExecutor { abstract class QueryExecutor {
GeneratedDatabase databaseInfo; GeneratedDatabase databaseInfo;
Future<bool> ensureOpen(); Future<bool> ensureOpen();
@ -50,4 +51,5 @@ abstract class QueryExecutor {
Future<int> runInsert(String statement, List<dynamic> args); Future<int> runInsert(String statement, List<dynamic> args);
Future<int> runUpdate(String statement, List<dynamic> args); Future<int> runUpdate(String statement, List<dynamic> args);
Future<int> runDelete(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> _defaultOnCreate(Migrator m) => m.createAllTables();
Future<void> _defaultOnUpdate(Migrator m, int from, int to) async => Future<void> _defaultOnUpdate(Migrator m, int from, int to) async =>
throw Exception("You've bumped the schema version for your sally database " 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 " "but didn't provide a strategy for schema updates. Please do that by "
'adapting the migrations getter in your database class.'); 'adapting the migrations getter in your database class.');
class MigrationStrategy { class MigrationStrategy {
final OnCreate onCreate; 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 { class Migrator {
final GeneratedDatabase _db; 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/sally.dart';
import 'package:sally/src/runtime/components/component.dart'; import 'package:sally/src/runtime/components/component.dart';
import 'package:sally/src/runtime/expressions/expression.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); 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 @override
Expression<BoolType> equals(Expression<S> compare) => Expression<BoolType> equals(Expression<S> compare) =>
Comparison.equal(this, compare); Comparison.equal(this, compare);
@ -31,12 +48,23 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
@override @override
Expression<BoolType> like(String regex) => Expression<BoolType> like(String regex) =>
LikeOperator(this, Variable<String, StringType>(regex)); LikeOperator(this, Variable<String, StringType>(regex));
@override
final String typeName = 'VARCHAR';
} }
class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType> class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
implements BoolColumn { implements BoolColumn {
GeneratedBoolColumn(String name, bool nullable) : super(name, nullable); 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 @override
void writeInto(GenerationContext context) { void writeInto(GenerationContext context) {
context.buffer.write('('); context.buffer.write('(');
@ -49,6 +77,9 @@ class GeneratedIntColumn extends GeneratedColumn<int, IntType>
implements IntColumn { implements IntColumn {
final bool hasAutoIncrement; final bool hasAutoIncrement;
@override
final String typeName = 'INTEGER';
GeneratedIntColumn(String name, bool nullable, GeneratedIntColumn(String name, bool nullable,
{this.hasAutoIncrement = false}) {this.hasAutoIncrement = false})
: super(name, nullable); : 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 /// The primary key of this table. Can be null if no custom primary key has
/// been specified /// been specified
Set<Column> get $primaryKey => null; Set<GeneratedColumn> get $primaryKey => null;
/// The table name in the sql table /// The table name in the sql table
String get $tableName; String get $tableName;
List<Column> get $columns; List<GeneratedColumn> get $columns;
DataClass map(Map<String, dynamic> data); DataClass map(Map<String, dynamic> data);
} }

View File

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

View File

@ -36,11 +36,13 @@ class FlutterQueryExecutor extends QueryExecutor {
resolvedPath, resolvedPath,
version: databaseInfo.schemaVersion, version: databaseInfo.schemaVersion,
onCreate: (db, version) { onCreate: (db, version) {
return databaseInfo.handleDatabaseCreation(_SqfliteExecutor(db)); return databaseInfo.handleDatabaseCreation(
executor: (sql) => db.execute(sql),
);
}, },
onUpgrade: (db, from, to) { onUpgrade: (db, from, to) {
return databaseInfo.handleDatabaseVersionChange( 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) { Future<int> runUpdate(String statement, List args) {
return _db.rawUpdate(statement, args); return _db.rawUpdate(statement, args);
} }
}
class _SqfliteExecutor extends QueryExecutor {
final Database _db;
_SqfliteExecutor(this._db);
@override @override
Future<bool> ensureOpen() async { Future<void> runCustom(String statement) {
return true; return _db.execute(statement);
}
@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);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -56,10 +56,10 @@ class SallyGenerator extends GeneratorForAnnotation<UseSally> {
} }
} }
if (_foundTables.isEmpty) if (_foundTables.isEmpty) return '';
return '';
final specifiedDb = SpecifiedDatabase(element as ClassElement, tablesForThisDb); final specifiedDb =
SpecifiedDatabase(element as ClassElement, tablesForThisDb);
final buffer = StringBuffer(); final buffer = StringBuffer();
DatabaseWriter(specifiedDb).write(buffer); 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. // Default behavior if the table name is not a valid plural.
return '${tableName}Data'; 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'; import 'package:sally_generator/src/writer/table_writer.dart';
class DatabaseWriter { class DatabaseWriter {
final SpecifiedDatabase db; final SpecifiedDatabase db;
DatabaseWriter(this.db); DatabaseWriter(this.db);
@ -17,7 +16,7 @@ class DatabaseWriter {
// Write the database class // Write the database class
final className = '_\$${db.fromClass.name}'; final className = '_\$${db.fromClass.name}';
buffer.write('abstract class $className extends GeneratedDatabase {\n' 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>[]; final tableGetters = <String>[];
@ -26,7 +25,8 @@ class DatabaseWriter {
tableGetters.add(tableFieldName); tableGetters.add(tableFieldName);
final tableClassName = table.tableInfoName; 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 // Write List of tables, close bracket for class
@ -35,4 +35,4 @@ class DatabaseWriter {
..write(tableGetters.join(',')) ..write(tableGetters.join(','))
..write('];\n}'); ..write('];\n}');
} }
} }

View File

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