diff --git a/packages/orm/angel_migration/README.md b/packages/orm/angel_migration/README.md index 7b31ebb5..3ce74bd9 100755 --- a/packages/orm/angel_migration/README.md +++ b/packages/orm/angel_migration/README.md @@ -11,7 +11,7 @@ This package contains the abstract classes for implementing database migration i * Create tables based on ORM models * Drop tables based on ORM models -* Add new tables based ORM models +* Add new tables based on ORM models ## Limitation diff --git a/packages/orm/angel_migration_runner/CHANGELOG.md b/packages/orm/angel_migration_runner/CHANGELOG.md index b0da4f72..0ddc92d3 100755 --- a/packages/orm/angel_migration_runner/CHANGELOG.md +++ b/packages/orm/angel_migration_runner/CHANGELOG.md @@ -4,9 +4,10 @@ * Updated README * Updated examples -* Added test cases for `PostgreSQL` -* Added test cases for `MySQL` -* Added test cases for `MariaDB` +* Updated `PostgresMigrationRunner` error handling +* Fixed `MySqlMigrationRunner` migration issues +* Added test cases for `PostgreSQL`, `MySQL` and `MariaDB` +* Added auto increment integer primary key suppport to MySQL and MariaDB ## 8.2.0 diff --git a/packages/orm/angel_migration_runner/lib/src/cli.dart b/packages/orm/angel_migration_runner/lib/src/cli.dart index 37cce4c6..b3c5abe7 100755 --- a/packages/orm/angel_migration_runner/lib/src/cli.dart +++ b/packages/orm/angel_migration_runner/lib/src/cli.dart @@ -50,6 +50,7 @@ class _RefreshCommand extends Command { @override String get name => 'refresh'; + @override String get description => 'Resets the database, and then re-runs all migrations.'; @@ -67,8 +68,9 @@ class _RollbackCommand extends Command { @override String get name => 'rollback'; + @override - String get description => 'Undoes the last batch of migrations.'; + String get description => 'Undo the last batch of migrations.'; final MigrationRunner migrationRunner; diff --git a/packages/orm/angel_migration_runner/lib/src/mariadb/runner.dart b/packages/orm/angel_migration_runner/lib/src/mariadb/runner.dart index 4a4623a1..a4a344eb 100644 --- a/packages/orm/angel_migration_runner/lib/src/mariadb/runner.dart +++ b/packages/orm/angel_migration_runner/lib/src/mariadb/runner.dart @@ -40,7 +40,7 @@ class MariaDbMigrationRunner implements MigrationRunner { await connection.query(''' CREATE TABLE IF NOT EXISTS migrations ( - id serial, + id integer NOT NULL AUTO_INCREMENT, batch integer, path varchar(255), PRIMARY KEY(id) diff --git a/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart b/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart index 748505a7..405e8273 100644 --- a/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart +++ b/packages/orm/angel_migration_runner/lib/src/mariadb/table.dart @@ -48,6 +48,12 @@ abstract class MariaDbGenerator { buf.write(' UNIQUE'); } else if (column.indexType == IndexType.primaryKey) { buf.write(' PRIMARY KEY'); + + // For int based primary key, apply NOT NULL + // and AUTO_INCREMENT + if (column.type == ColumnType.int) { + buf.write(' NOT NULL AUTO_INCREMENT'); + } } for (var ref in column.externalReferences) { @@ -105,7 +111,7 @@ class MariaDbTable extends Table { if (indexBuf.isNotEmpty) { for (var i = 0; i < indexBuf.length; i++) { - buf.write(',\n${indexBuf[$1]}'); + buf.write(',\n${indexBuf[i]}'); } } } diff --git a/packages/orm/angel_migration_runner/lib/src/mysql/runner.dart b/packages/orm/angel_migration_runner/lib/src/mysql/runner.dart index ad50fe56..ab2020c2 100644 --- a/packages/orm/angel_migration_runner/lib/src/mysql/runner.dart +++ b/packages/orm/angel_migration_runner/lib/src/mysql/runner.dart @@ -42,15 +42,18 @@ class MySqlMigrationRunner implements MigrationRunner { await connection.execute(''' CREATE TABLE IF NOT EXISTS migrations ( - id serial, + id integer NOT NULL AUTO_INCREMENT, batch integer, - path varchar(255), + path varchar(500), PRIMARY KEY(id) ); ''').then((result) { + //print(result); _log.info('Check and create "migrations" table'); }).catchError((e) { + //print(e); _log.severe('Failed to create "migrations" table.'); + throw e; }); } @@ -59,8 +62,9 @@ class MySqlMigrationRunner implements MigrationRunner { await _init(); var result = await connection.execute('SELECT path from migrations;'); var existing = []; - if (result.rows.isNotEmpty) { - existing = result.rows.first.assoc().values.cast().toList(); + for (var item in result.rows) { + var rec = item.assoc().values.first ?? ""; + existing.add(rec.replaceAll("\\", "\\\\")); } var toRun = []; @@ -113,8 +117,9 @@ class MySqlMigrationRunner implements MigrationRunner { result = await connection .execute('SELECT path from migrations WHERE batch = $curBatch;'); var existing = []; - if (result.rows.isNotEmpty) { - existing = result.rows.first.assoc().values.cast().toList(); + for (var item in result.rows) { + var rec = item.assoc().values.first ?? ""; + existing.add(rec.replaceAll("\\", "\\\\")); } var toRun = []; @@ -143,11 +148,15 @@ class MySqlMigrationRunner implements MigrationRunner { await _init(); var result = await connection .execute('SELECT path from migrations ORDER BY batch DESC;'); + + // "mysql_client" driver will auto convert path containing "\\" to "\". + // So need to revert "\" back to "\\" for the migration logic to work var existing = []; - if (result.rows.isNotEmpty) { - var firstRow = result.rows.first; - existing = firstRow.assoc().values.cast().toList(); + for (var item in result.rows) { + var rec = item.assoc().values.first ?? ""; + existing.add(rec.replaceAll("\\", "\\\\")); } + var toRun = existing.where(migrations.containsKey).toList(); if (toRun.isNotEmpty) { diff --git a/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart b/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart index 8aff3a19..0182e6f9 100644 --- a/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart +++ b/packages/orm/angel_migration_runner/lib/src/mysql/schema.dart @@ -22,11 +22,13 @@ class MySqlSchema extends Schema { await connection.transactional((ctx) async { var sql = compile(); var result = await ctx.execute(sql).catchError((e) { + print(e); _log.severe('Failed to run query: [ $sql ]', e); throw Exception(e); }); affectedRows = result.affectedRows.toInt(); }).catchError((e) { + print(e); _log.severe('Failed to run query in a transaction', e); }); diff --git a/packages/orm/angel_migration_runner/lib/src/mysql/table.dart b/packages/orm/angel_migration_runner/lib/src/mysql/table.dart index 407f7358..dd2520db 100644 --- a/packages/orm/angel_migration_runner/lib/src/mysql/table.dart +++ b/packages/orm/angel_migration_runner/lib/src/mysql/table.dart @@ -10,6 +10,7 @@ abstract class MySqlGenerator { if (column.type == ColumnType.timeStamp) { str = ColumnType.dateTime.name; } + if (column.type.hasLength) { return '$str(${column.length})'; } else { @@ -20,7 +21,11 @@ abstract class MySqlGenerator { static String compileColumn(MigrationColumn column) { var buf = StringBuffer(columnType(column)); - if (column.isNullable == false) buf.write(' NOT NULL'); + if (!column.isNullable) { + buf.write(' NOT NULL'); + } + + // Default value if (column.defaultValue != null) { String s; var value = column.defaultValue; @@ -47,6 +52,12 @@ abstract class MySqlGenerator { buf.write(' UNIQUE'); } else if (column.indexType == IndexType.primaryKey) { buf.write(' PRIMARY KEY'); + + // For int based primary key, apply NOT NULL + // and AUTO_INCREMENT + if (column.type == ColumnType.int) { + buf.write(' NOT NULL AUTO_INCREMENT'); + } } for (var ref in column.externalReferences) { @@ -104,9 +115,11 @@ class MysqlTable extends Table { if (indexBuf.isNotEmpty) { for (var i = 0; i < indexBuf.length; i++) { - buf.write(',\n${indexBuf[$1]}'); + buf.write(',\n${indexBuf[i]}'); } } + + print(buf); } } diff --git a/packages/orm/angel_migration_runner/lib/src/postgres/runner.dart b/packages/orm/angel_migration_runner/lib/src/postgres/runner.dart index 58590110..05a6344e 100755 --- a/packages/orm/angel_migration_runner/lib/src/postgres/runner.dart +++ b/packages/orm/angel_migration_runner/lib/src/postgres/runner.dart @@ -45,7 +45,7 @@ class PostgresMigrationRunner implements MigrationRunner { CREATE TABLE IF NOT EXISTS "migrations" ( id serial, batch integer, - path varchar, + path varchar(500), PRIMARY KEY(id) ); ''').then((result) { diff --git a/packages/orm/angel_migration_runner/test/mariadb_test.dart b/packages/orm/angel_migration_runner/test/mariadb_test.dart index 088affdf..19edc8dd 100644 --- a/packages/orm/angel_migration_runner/test/mariadb_test.dart +++ b/packages/orm/angel_migration_runner/test/mariadb_test.dart @@ -5,7 +5,7 @@ import 'package:angel3_migration_runner/mariadb.dart'; import 'package:mysql1/mysql1.dart'; import 'package:test/test.dart'; -import 'models/todo.dart'; +import 'models/mysql_todo.dart'; void main() async { late MySqlConnection conn; diff --git a/packages/orm/angel_migration_runner/test/models/mysql_todo.dart b/packages/orm/angel_migration_runner/test/models/mysql_todo.dart new file mode 100644 index 00000000..178aef4b --- /dev/null +++ b/packages/orm/angel_migration_runner/test/models/mysql_todo.dart @@ -0,0 +1,53 @@ +import 'package:angel3_migration/angel3_migration.dart'; +import 'package:angel3_orm/angel3_orm.dart'; + +class UserMigration implements Migration { + @override + void up(Schema schema) { + schema.create('users', (table) { + table + ..integer('id').primaryKey() + ..varChar('username', length: 32).unique() + ..varChar('password') + ..boolean('account_confirmed').defaultsTo(false); + }); + } + + @override + void down(Schema schema) { + schema.drop('users'); + } +} + +class TodoMigration implements Migration { + @override + void up(Schema schema) { + schema.create('todos', (table) { + table + ..integer('id').primaryKey() + ..integer('user_id').references('users', 'id').onDeleteCascade() + ..varChar('text') + ..boolean('completed').defaultsTo(false); + }); + } + + @override + void down(Schema schema) { + schema.drop('todos'); + } +} + +class ItemMigration extends Migration { + @override + void up(Schema schema) { + schema.create('items', (table) { + table + ..integer('id').primaryKey() + ..varChar('name', length: 64) + ..timeStamp('created_at').defaultsTo(currentTimestamp); + }); + } + + @override + void down(Schema schema) => schema.drop('items'); +} diff --git a/packages/orm/angel_migration_runner/test/models/todo.dart b/packages/orm/angel_migration_runner/test/models/pg_todo.dart similarity index 100% rename from packages/orm/angel_migration_runner/test/models/todo.dart rename to packages/orm/angel_migration_runner/test/models/pg_todo.dart diff --git a/packages/orm/angel_migration_runner/test/mysql_test.dart b/packages/orm/angel_migration_runner/test/mysql_test.dart index 8d2e49f7..ff062397 100644 --- a/packages/orm/angel_migration_runner/test/mysql_test.dart +++ b/packages/orm/angel_migration_runner/test/mysql_test.dart @@ -5,7 +5,7 @@ import 'package:angel3_migration_runner/mysql.dart'; import 'package:mysql_client/mysql_client.dart'; import 'package:test/test.dart'; -import 'models/todo.dart'; +import 'models/mysql_todo.dart'; void main() async { late MySQLConnection conn; @@ -47,6 +47,11 @@ void main() async { print("Test migration up"); await runner.up(); }); + + test('reset', () async { + print("Test migration reset"); + await runner.reset(); + }); }); tearDown(() async { diff --git a/packages/orm/angel_migration_runner/test/pg_test.dart b/packages/orm/angel_migration_runner/test/pg_test.dart index 25c3f962..8075bcf3 100644 --- a/packages/orm/angel_migration_runner/test/pg_test.dart +++ b/packages/orm/angel_migration_runner/test/pg_test.dart @@ -5,7 +5,7 @@ import 'package:angel3_migration_runner/postgres.dart'; import 'package:postgres/postgres.dart'; import 'package:test/test.dart'; -import 'models/todo.dart'; +import 'models/pg_todo.dart'; void main() async { late Connection conn;