PersistenceJS: обновление существующей базы данных с помощью migrate()
В настоящее время я пытаюсь внести изменения в существующую БД с помощью плагина миграции для PersistenceJS. Я могу добавлять / редактировать / удалять элементы в БД просто отлично - но…
- Как добавить столбец в существующую (!) Таблицу?
Как изменить тип существующего (!) Столбца, например, с "text" на "integer"?
These changes should retain currently existing data.
К сожалению, документации немного, может быть, вы могли бы помочь?
Вот текущая, рабочая установка:
persistence.store.websql.config(persistence, 'tododatabase', 'todos are fun', 5*1024*1024);
var Todo = persistence.define('Todo', {
task: 'TEXT',
priority: 'INT',
done: 'BOOL'
});
persistence.schemaSync();
function addTodo( item ){
var todo = new Todo();
todo.task = item.task;
todo.priority = item.priority;
todo.done = item.done;
persistence.add(todo);
persistence.flush();
};
function deleteTodo( item, callback ){
// item.id was created automatically by calling "new Todo()"
Todo.all().filter('id','=', item.id ).destroyAll( function(){
persistence.flush( callback );
});
};
Код миграции, который вроде работает:
persistence.defineMigration(1, {
up: function() {
this.createTable('Todo', function(t){
t.text('task');
t.integer('priority');
t.boolean('done');
});
},
down: function() {
this.dropTable('Todo');
}
});
persistence.defineMigration(2, {
up: function() {
this.addColumn('Todo', 'due', 'DATE');
},
down: function() {
this.removeColumn('Todo', 'due');
}
});
function migrate( callback ){
console.log('migrating...');
persistence.migrations.init( function(){
console.log('migration init');
// this should migrate up to the latest version, in our case: 2
persistence.migrate( function(){
console.log('migration complete!');
} );
});
}
Результаты…
- вызов migrate() будет регистрироваться только до "init миграции", полный обработчик никогда не вызывается, столбец "due" не создается
- не вызывая schemaSync() перед вызовом migrate(), как сам Zef Hemel, предложенный в этом посте, дает тот же результат, что и 1.
- изменив первую строку на
persistence.store.websql.config(persistence, 'newdatabase', 'testing migration', 5*1024*1024);
, не вызывая schemaSync() и только вызывая migrate(), будет успешно регистрировать "миграция завершена!" - но это происходит в новой, совершенно пустой базе данных "newdatabase", которая, конечно, не будет содержать никаких ожидающих данных.
Резюме
Существует база данных, которая была создана с использованием persistence.store.websql.config(...)
, persistence.define('Todo',...)
а также persistence.schemaSync()
,
Теперь я хочу сохранить все данные, которые уже существуют в этой базе данных, но хочу
- изменить тип столбца
priority
от "целое" до "текст" - добавить столбец
due
с типом "дата" для всех существующих Todos
Если бы вы могли подтолкнуть меня в правильном направлении, я был бы очень признателен!
Спасибо!
2 ответа
Я наконец получил это работает. Существует ряд проблем с моими первоначальными требованиями, на которые я хотел бы обратить внимание в будущем. Взгляните на первое определение миграции:
persistence.defineMigration(1, {
up: function() {
this.createTable('Todo', function(t){
...
Не удивительно, createTable
будет делать именно это: он будет выполнять инструкцию SQL 'CREATE TABLE Todo ...'
, который молча завершится ошибкой и остановит миграцию, если есть таблица с именем Todo
уже. Вот почему он работал с новой базой данных, но не с существующей. Имейте в виду: у меня уже была живая база данных с таблицей "Todo", которая нуждалась в обновлении. Если вы начинаете заново (то есть вы не использовали schemaSync
), createTable
работает просто отлично. Поскольку плагин Migrations не предоставляет createTableIfNotExists
метод, мне нужно было использовать executeSql
следующее:
persistence.defineMigration(1, {
up: function() {
this.executeSql('CREATE TABLE IF NOT EXISTS Todo (id VARCHAR(32) PRIMARY KEY, task TEXT, priority INT, done BOOL)');
...
Теперь, когда миграция из версии 0 в схему прошла успешно, миграция в версию 2 также прошла успешно.
При переходе на версию 3 тип priority
столбец необходимо изменить с int
в text
, Обычно это делается с помощью ALTER COLUMN
Команда SQL, которая не поддерживается Web SQL / SQLite. Смотрите опущенные функции для SQLite.
Изменение столбца с помощью SQLite требует четырехэтапного обходного пути:
persistence.defineMigration(3, {
up: function() {
// rename current table
this.executeSql('ALTER TABLE Todo RENAME TO OldTodo');
// create new table with required columns and column types
this.executeSql('CREATE TABLE Todo (id VARCHAR(32) PRIMARY KEY, task TEXT, priority TEXT, done BOOL)');
// copy contents from old table to new table
this.executeSql('INSERT INTO Todo(id, task, priority, done) SELECT id, task, priority, done FROM OldTodo');
// delete old table
this.executeSql('DROP TABLE OldTodo');
},
...
Конечно, после изменения типа столбца определение сущности для Todo также должно быть изменено:
var Todo = persistence.define('Todo', {
task: 'TEXT',
priority: 'TEXT', // was 'INT'
due: 'DATE',
done: 'BOOL'
});
И, наконец, полный источник:
persistence.store.websql.config(persistence, 'tododatabase', 'todos are fun', 5*1024*1024);
// persistence.debug = true;
//v0 + v1
// var Todo = persistence.define('Todo', {
// task: 'TEXT',
// priority: 'INT',
// done: 'BOOL'
// });
//v2
// var Todo = persistence.define('Todo', {
// task: 'TEXT',
// priority: 'INT',
// due: 'DATE',
// done: 'BOOL'
// });
//v3
var Todo = persistence.define('Todo', {
task: 'TEXT',
priority: 'TEXT',
due: 'DATE',
done: 'BOOL'
});
persistence.defineMigration(1, {
up: function() {
this.executeSql('CREATE TABLE IF NOT EXISTS Todo (id VARCHAR(32) PRIMARY KEY, task TEXT, priority INT, done BOOL)');
},
down: function() {
this.dropTable('Todo');
}
});
persistence.defineMigration(2, {
up: function() {
this.addColumn('Todo', 'due', 'DATE');
},
down: function() {
this.removeColumn('Todo', 'due');
}
});
persistence.defineMigration(3, {
up: function() {
// rename current table
this.executeSql('ALTER TABLE Todo RENAME TO OldTodo');
// create new table with required columns
this.executeSql('CREATE TABLE Todo (id VARCHAR(32) PRIMARY KEY, task TEXT, priority TEXT, due DATE, done BOOL)');
// copy contents from old table to new table
this.executeSql('INSERT INTO Todo(id, task, priority, due, done) SELECT id, task, priority, due, done FROM OldTodo');
// delete current table
this.executeSql('DROP TABLE OldTodo');
},
down: function() {
this.executeSql('ALTER TABLE Todo RENAME TO OldTodo');
this.executeSql('CREATE TABLE Todo (id VARCHAR(32) PRIMARY KEY, task TEXT, priority INT, due DATE, done BOOL)');
this.executeSql('INSERT INTO Todo(id, task, priority, due, done) SELECT id, task, priority, due, done FROM OldTodo');
this.executeSql('DROP TABLE OldTodo');
}
});
function migrate( callback ){
console.log('migrating...');
persistence.migrations.init( function(){
console.log('migration init');
persistence.migrate( function(){
console.debug('migration complete!');
callback();
} );
});
};
migrate( onMigrationComplete );
function onMigrationComplete(){
// database is ready. do amazing things...
};
Это отличное объяснение, спасибо! Но я думаю, что знаю более простой способ добиться этого.
У меня такая же проблема, как и у вас: у меня есть набор схем, описанных persistence.define
и создан с persistence.schemaSync
,
Так что это мой частный случай:
// This is my mixin for all schemas
var Versioned = persistence.defineMixin('Versioned', {
serverId: "TEXT",
intVersion: "INT",
dtSynced: "DATE",
dtCreatedAt: "DATE",
dtUpdatedAt: "DATE",
delete: "BOOL",
update: "BOOL",
add: "BOOL",
isReadOnly: "BOOL"
});
// This is one of the schemas I need to update with a new field.
var Person = persistence.define('Person', {
fullName: "TEXT",
rate: "INT"
});
//... More schema definitions
// Setup mixin
Person.is(Versioned);
// Sync schemas
persistence.schemaSync();
Хорошо. Ничего особенного в этом нет. Теперь, через несколько месяцев, когда мое приложение работает, я хочу добавить новое поле. isEmployed
к Person
схемы.
В соответствии с документами, я должен переписать все мои определения схемы для миграций и прекратить использование persistence.schemaSync()
, Но я не хочу переписывать все мои определения. Вместо этого я определяю новую миграцию прямо за кодом инициализации PersistenceJS:
// Init ORM
persistence.store.websql.config(
persistence,
'Sarafan',
'0.0.2',
'Sarafan.app database',
100 * 1024 * 1024,
0
);
// Define Migrations
persistence.defineMigration(1, {
up: function () {
this.addColumn('Person', 'isEmployed', 'BOOL');
}
});
// ... describing isVersioned mixin
// Updated schema definition with a new field 'isEmployed'
var Person = persistence.define('Person', {
fullName: "TEXT",
rate: "INT",
isEmployed: "BOOL"
});
//... More schema definitions
// Setup mixin
Person.is(Versioned);
// Apply the migration right away from the schemaSync call.
persistence.schemaSync(function (tx) {
persistence.migrations.init(function () {
persistence.migrate(function(){
// Optional callback to be executed after initialization
});
});
});
Итак, это все! Я протестировал этот подход только для добавления новых полей в схему.
Дайте мне знать, если это работает или не работает для вас.