Fixed MySQL migration
This commit is contained in:
parent
7e42a7e317
commit
5ed443ae33
14 changed files with 113 additions and 22 deletions
|
@ -11,7 +11,7 @@ This package contains the abstract classes for implementing database migration i
|
||||||
|
|
||||||
* Create tables based on ORM models
|
* Create tables based on ORM models
|
||||||
* Drop 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
|
## Limitation
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
|
|
||||||
* Updated README
|
* Updated README
|
||||||
* Updated examples
|
* Updated examples
|
||||||
* Added test cases for `PostgreSQL`
|
* Updated `PostgresMigrationRunner` error handling
|
||||||
* Added test cases for `MySQL`
|
* Fixed `MySqlMigrationRunner` migration issues
|
||||||
* Added test cases for `MariaDB`
|
* Added test cases for `PostgreSQL`, `MySQL` and `MariaDB`
|
||||||
|
* Added auto increment integer primary key suppport to MySQL and MariaDB
|
||||||
|
|
||||||
## 8.2.0
|
## 8.2.0
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ class _RefreshCommand extends Command {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => 'refresh';
|
String get name => 'refresh';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get description =>
|
String get description =>
|
||||||
'Resets the database, and then re-runs all migrations.';
|
'Resets the database, and then re-runs all migrations.';
|
||||||
|
@ -67,8 +68,9 @@ class _RollbackCommand extends Command {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => 'rollback';
|
String get name => 'rollback';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get description => 'Undoes the last batch of migrations.';
|
String get description => 'Undo the last batch of migrations.';
|
||||||
|
|
||||||
final MigrationRunner migrationRunner;
|
final MigrationRunner migrationRunner;
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ class MariaDbMigrationRunner implements MigrationRunner {
|
||||||
|
|
||||||
await connection.query('''
|
await connection.query('''
|
||||||
CREATE TABLE IF NOT EXISTS migrations (
|
CREATE TABLE IF NOT EXISTS migrations (
|
||||||
id serial,
|
id integer NOT NULL AUTO_INCREMENT,
|
||||||
batch integer,
|
batch integer,
|
||||||
path varchar(255),
|
path varchar(255),
|
||||||
PRIMARY KEY(id)
|
PRIMARY KEY(id)
|
||||||
|
|
|
@ -48,6 +48,12 @@ abstract class MariaDbGenerator {
|
||||||
buf.write(' UNIQUE');
|
buf.write(' UNIQUE');
|
||||||
} else if (column.indexType == IndexType.primaryKey) {
|
} else if (column.indexType == IndexType.primaryKey) {
|
||||||
buf.write(' PRIMARY KEY');
|
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) {
|
for (var ref in column.externalReferences) {
|
||||||
|
@ -105,7 +111,7 @@ class MariaDbTable extends Table {
|
||||||
|
|
||||||
if (indexBuf.isNotEmpty) {
|
if (indexBuf.isNotEmpty) {
|
||||||
for (var i = 0; i < indexBuf.length; i++) {
|
for (var i = 0; i < indexBuf.length; i++) {
|
||||||
buf.write(',\n${indexBuf[$1]}');
|
buf.write(',\n${indexBuf[i]}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,15 +42,18 @@ class MySqlMigrationRunner implements MigrationRunner {
|
||||||
|
|
||||||
await connection.execute('''
|
await connection.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS migrations (
|
CREATE TABLE IF NOT EXISTS migrations (
|
||||||
id serial,
|
id integer NOT NULL AUTO_INCREMENT,
|
||||||
batch integer,
|
batch integer,
|
||||||
path varchar(255),
|
path varchar(500),
|
||||||
PRIMARY KEY(id)
|
PRIMARY KEY(id)
|
||||||
);
|
);
|
||||||
''').then((result) {
|
''').then((result) {
|
||||||
|
//print(result);
|
||||||
_log.info('Check and create "migrations" table');
|
_log.info('Check and create "migrations" table');
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
|
//print(e);
|
||||||
_log.severe('Failed to create "migrations" table.');
|
_log.severe('Failed to create "migrations" table.');
|
||||||
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +62,9 @@ class MySqlMigrationRunner implements MigrationRunner {
|
||||||
await _init();
|
await _init();
|
||||||
var result = await connection.execute('SELECT path from migrations;');
|
var result = await connection.execute('SELECT path from migrations;');
|
||||||
var existing = <String>[];
|
var existing = <String>[];
|
||||||
if (result.rows.isNotEmpty) {
|
for (var item in result.rows) {
|
||||||
existing = result.rows.first.assoc().values.cast<String>().toList();
|
var rec = item.assoc().values.first ?? "";
|
||||||
|
existing.add(rec.replaceAll("\\", "\\\\"));
|
||||||
}
|
}
|
||||||
var toRun = <String>[];
|
var toRun = <String>[];
|
||||||
|
|
||||||
|
@ -113,8 +117,9 @@ class MySqlMigrationRunner implements MigrationRunner {
|
||||||
result = await connection
|
result = await connection
|
||||||
.execute('SELECT path from migrations WHERE batch = $curBatch;');
|
.execute('SELECT path from migrations WHERE batch = $curBatch;');
|
||||||
var existing = <String>[];
|
var existing = <String>[];
|
||||||
if (result.rows.isNotEmpty) {
|
for (var item in result.rows) {
|
||||||
existing = result.rows.first.assoc().values.cast<String>().toList();
|
var rec = item.assoc().values.first ?? "";
|
||||||
|
existing.add(rec.replaceAll("\\", "\\\\"));
|
||||||
}
|
}
|
||||||
var toRun = <String>[];
|
var toRun = <String>[];
|
||||||
|
|
||||||
|
@ -143,11 +148,15 @@ class MySqlMigrationRunner implements MigrationRunner {
|
||||||
await _init();
|
await _init();
|
||||||
var result = await connection
|
var result = await connection
|
||||||
.execute('SELECT path from migrations ORDER BY batch DESC;');
|
.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>[];
|
var existing = <String>[];
|
||||||
if (result.rows.isNotEmpty) {
|
for (var item in result.rows) {
|
||||||
var firstRow = result.rows.first;
|
var rec = item.assoc().values.first ?? "";
|
||||||
existing = firstRow.assoc().values.cast<String>().toList();
|
existing.add(rec.replaceAll("\\", "\\\\"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var toRun = existing.where(migrations.containsKey).toList();
|
var toRun = existing.where(migrations.containsKey).toList();
|
||||||
|
|
||||||
if (toRun.isNotEmpty) {
|
if (toRun.isNotEmpty) {
|
||||||
|
|
|
@ -22,11 +22,13 @@ class MySqlSchema extends Schema {
|
||||||
await connection.transactional((ctx) async {
|
await connection.transactional((ctx) async {
|
||||||
var sql = compile();
|
var sql = compile();
|
||||||
var result = await ctx.execute(sql).catchError((e) {
|
var result = await ctx.execute(sql).catchError((e) {
|
||||||
|
print(e);
|
||||||
_log.severe('Failed to run query: [ $sql ]', e);
|
_log.severe('Failed to run query: [ $sql ]', e);
|
||||||
throw Exception(e);
|
throw Exception(e);
|
||||||
});
|
});
|
||||||
affectedRows = result.affectedRows.toInt();
|
affectedRows = result.affectedRows.toInt();
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
|
print(e);
|
||||||
_log.severe('Failed to run query in a transaction', e);
|
_log.severe('Failed to run query in a transaction', e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ abstract class MySqlGenerator {
|
||||||
if (column.type == ColumnType.timeStamp) {
|
if (column.type == ColumnType.timeStamp) {
|
||||||
str = ColumnType.dateTime.name;
|
str = ColumnType.dateTime.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column.type.hasLength) {
|
if (column.type.hasLength) {
|
||||||
return '$str(${column.length})';
|
return '$str(${column.length})';
|
||||||
} else {
|
} else {
|
||||||
|
@ -20,7 +21,11 @@ abstract class MySqlGenerator {
|
||||||
static String compileColumn(MigrationColumn column) {
|
static String compileColumn(MigrationColumn column) {
|
||||||
var buf = StringBuffer(columnType(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) {
|
if (column.defaultValue != null) {
|
||||||
String s;
|
String s;
|
||||||
var value = column.defaultValue;
|
var value = column.defaultValue;
|
||||||
|
@ -47,6 +52,12 @@ abstract class MySqlGenerator {
|
||||||
buf.write(' UNIQUE');
|
buf.write(' UNIQUE');
|
||||||
} else if (column.indexType == IndexType.primaryKey) {
|
} else if (column.indexType == IndexType.primaryKey) {
|
||||||
buf.write(' PRIMARY KEY');
|
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) {
|
for (var ref in column.externalReferences) {
|
||||||
|
@ -104,9 +115,11 @@ class MysqlTable extends Table {
|
||||||
|
|
||||||
if (indexBuf.isNotEmpty) {
|
if (indexBuf.isNotEmpty) {
|
||||||
for (var i = 0; i < indexBuf.length; i++) {
|
for (var i = 0; i < indexBuf.length; i++) {
|
||||||
buf.write(',\n${indexBuf[$1]}');
|
buf.write(',\n${indexBuf[i]}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class PostgresMigrationRunner implements MigrationRunner {
|
||||||
CREATE TABLE IF NOT EXISTS "migrations" (
|
CREATE TABLE IF NOT EXISTS "migrations" (
|
||||||
id serial,
|
id serial,
|
||||||
batch integer,
|
batch integer,
|
||||||
path varchar,
|
path varchar(500),
|
||||||
PRIMARY KEY(id)
|
PRIMARY KEY(id)
|
||||||
);
|
);
|
||||||
''').then((result) {
|
''').then((result) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:angel3_migration_runner/mariadb.dart';
|
||||||
import 'package:mysql1/mysql1.dart';
|
import 'package:mysql1/mysql1.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'models/todo.dart';
|
import 'models/mysql_todo.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
late MySqlConnection conn;
|
late MySqlConnection conn;
|
||||||
|
|
|
@ -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');
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import 'package:angel3_migration_runner/mysql.dart';
|
||||||
import 'package:mysql_client/mysql_client.dart';
|
import 'package:mysql_client/mysql_client.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'models/todo.dart';
|
import 'models/mysql_todo.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
late MySQLConnection conn;
|
late MySQLConnection conn;
|
||||||
|
@ -47,6 +47,11 @@ void main() async {
|
||||||
print("Test migration up");
|
print("Test migration up");
|
||||||
await runner.up();
|
await runner.up();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('reset', () async {
|
||||||
|
print("Test migration reset");
|
||||||
|
await runner.reset();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:angel3_migration_runner/postgres.dart';
|
||||||
import 'package:postgres/postgres.dart';
|
import 'package:postgres/postgres.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'models/todo.dart';
|
import 'models/pg_todo.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
late Connection conn;
|
late Connection conn;
|
||||||
|
|
Loading…
Reference in a new issue