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

Результаты…

  1. вызов migrate() будет регистрироваться только до "init миграции", полный обработчик никогда не вызывается, столбец "due" не создается
  2. не вызывая schemaSync() перед вызовом migrate(), как сам Zef Hemel, предложенный в этом посте, дает тот же результат, что и 1.
  3. изменив первую строку на 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
        });
      });
    });

Итак, это все! Я протестировал этот подход только для добавления новых полей в схему.

Дайте мне знать, если это работает или не работает для вас.

Другие вопросы по тегам