Fixed MySQL migration

This commit is contained in:
Thomas Hii 2024-07-20 15:37:25 +08:00
parent 7e42a7e317
commit 5ed443ae33
14 changed files with 113 additions and 22 deletions

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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)

View file

@ -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]}');
}
}
}

View file

@ -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 = <String>[];
if (result.rows.isNotEmpty) {
existing = result.rows.first.assoc().values.cast<String>().toList();
for (var item in result.rows) {
var rec = item.assoc().values.first ?? "";
existing.add(rec.replaceAll("\\", "\\\\"));
}
var toRun = <String>[];
@ -113,8 +117,9 @@ class MySqlMigrationRunner implements MigrationRunner {
result = await connection
.execute('SELECT path from migrations WHERE batch = $curBatch;');
var existing = <String>[];
if (result.rows.isNotEmpty) {
existing = result.rows.first.assoc().values.cast<String>().toList();
for (var item in result.rows) {
var rec = item.assoc().values.first ?? "";
existing.add(rec.replaceAll("\\", "\\\\"));
}
var toRun = <String>[];
@ -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 = <String>[];
if (result.rows.isNotEmpty) {
var firstRow = result.rows.first;
existing = firstRow.assoc().values.cast<String>().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) {

View file

@ -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);
});

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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');
}

View file

@ -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 {

View file

@ -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;