?

Log in

No account? Create an account
tech

Symfony & SQL Migrations

Если Вы не знаете, что такое миграции (migrations), посмотрите вот этот ролик (да-да это из тех ruby envy commercials).

Думаю, что любой разработчик веб-приложений проходил через довольно неприятный процесс синхронизации структуры БД на девелоперской машине и на продакшен. Что до меня, так я очень долго не принимал мер против такого безобразия и апдейтил руками новые колонки, таблицы или данные.

Если код может быть под svn, где можно выкатывать любую точку разработки и где хранится вся история изменений, то база данных таких возможностей не дает. Однако для организации самой примитивной схемы миграций ничего особенного не нужно — достаточно сделать папку для нумерованных sql-дампов, где писать изменения текущей итерации. Нумеровать подряд, не связываясь нумерацией ревизий svn. В базе иметь служебную табличку с одним полем — текущей миграции. При обновлении смотреть текущую миграцию, и заливать подряд необходимые дампы. Зато теперь история изменений нашей БД лежит в репозитории, что удобно — вытащил коллега новый апдейт, прокатил миграции и имеет актуальную рабочую копию.

Конечно было бы правильным, если бы в Symfony были родные инструменты для миграций. Однако их нет, зато есть небольшой костыль от коммунити, который позволяет жить с миграциями удобнее, чем просто складывать дампы.

Описание инструмента можно посмотреть здесь. Суть механизма в том, что для каждой миграции вместо дампа делается класс вида

class Migration001 extends sfMigration {
  
public function up() 
  {
    
$this->executeSQL('ALTER TABLE `question` ADD `user_id` INT NOT NULL AFTER `id`');
  }

  
public function down() 
  {
    
$this->executeSQL('ALTER TABLE `question` DROP `user_id`');
  }
}

Далее пакетный файл читает из служебной таблицы в базе номер текущей миграции и прокатывает нужное количество классов как до последней миграции, так и для любого заданного номера. Как вперед, так и назад.

Классы используют Propel только в виде коннекта к базе, так, что переделать под Доктрину да и просто отторвать этот инструмент от Симфони должно быть несложно.

Неудобно то, что файлы залиты приложением в трак-вики и вместо скачать предлагают посмотреть исходник. Приходится копипастить в текстовый редактор и вычищать номера строк. Я там нашел небольшой баг — при первом запуске при создании заготовки для миграции программа кидается исключениями пытаясь вычислить максимальный номер миграции на основе файлов, которых пока нет. Пофиксил. Запаковал. Выложил

Comments

Хммм... раньше не особо задумывался об этом. А если есть допустим Migration0001 и Migration0003, а 0002 нету? Мне кажется, что лучше все-таки хранить полную структуру таблицы и вносить расхождения с текущей. Тогда во-первых, не нужны будут предыдущие версии Migration****, а во-вторых, не надо будет хранить текущую версию, ибо она нас не волнует, достаточно получить текущую структуру у самой базы.

Пока не могу понять, почему выбран именно этот механизм, неужели вычисление разницы столь уж сложная задача?
>А если есть допустим Migration0001 и Migration0003, а 0002 нету

Это не правильно -- такого быть не должно. Номера миграций идут подряд, с номерами ревизий они не связаны.

Нсчет столь уж сложная задача. Не сложная. Но раздражающая.

Теперь насчет полной структуры.

Вот смотрите работаю я и еще вася -- у каждого рабочая станция плюс есть продакшен. Я внес изменения в базу и в код. Закоммитил. Вася слил мой апдейт из svn. Если есть механизм миграций, то вася просто запустит после апдейта php batch/migrate.php и все таблицы у него станут в соответствии с моим апдейтом. Поработали дошли то релиза, обновили на продакшене. Прокатили миграции и опять все работает. Если вдруг захотелось откатиться обратно, откатили код из свн, отактили миграции и опять все работает.

Это удобно.

А чем от этого о тличается "хранить полную структуру таблицы и вносить расхождения с текущей"? Действительно -- храним исходную структуру и историю изменений.
Это не правильно -- такого быть не должно. Номера миграций идут подряд, с номерами ревизий они не связаны.
В смысле они так попадают в репозиторий и распространяются вместе с другими пакетами? То есть что бы откатиться к первой версии мало выкачать первую версию, надо взять список миграций, ручками перенести и запустить, что бы откатить к началу?

Нсчет столь уж сложная задача. Не сложная. Но раздражающая.
Хмм... я имею ввиду написать алгоритм анализа, а не руками каждый раз разницу вводить :)

Прокатили миграции и опять все работает. Если вдруг захотелось откатиться обратно, откатили код из свн, отактили миграции и опять все работает.
Если я правильно понял, то не получится откатить назад без самого последнего файла миграции, которого в предыдущей ревизии в репозитории не было. То есть всегда надо иметь полный набор миграций.

А чем от этого о тличается "хранить полную структуру таблицы и вносить расхождения с текущей"? Действительно -- храним исходную структуру и историю изменений.
Ну так историю хранить не надо тогда, если её можно вычислить, если каждая ветка несет свою структуру, то всегда можно двинуться с любой до любой, надо только предоставить некоему скрипту две структуры для сравнения :-/
>В смысле они так попадают в репозиторий...
Да, именно так.

Да, чтобы откатиться с 10 миграции на 5 нужно перед откатом кода откатить миграции. Да, нужен полный набор, чтобы откатиться назад.

Я правильно понял, что предлагается хранить полную структуру (схему) в каждой ревизии и вычислять разницу? Т.е. вместо хранения изменений вы их будете вычислять на лету. Да можно. Но этот дифф на sql дампы нужно писать. А для приведенного механизма его писать не надо :) Или он есть где-то готовый?

В простой ситуации, -- я залил апдейт, а вася его вынул из репозитория. По-вашему надо запустить анализатор сравнения старой структуры и новой, только что приехавшей (кстати новая структура приехав из svn ведь затрет старый файл) и внести изменения в БД. Ну собственно похожее решение, только более трудоемкое, если его писать.

Опять же Вася вынул апдейт и может посмотреть что в базе поменялось -- глядя в файл миграции. А в Вашем случае ему нужно будет это вычислить.

Я правильно понял, что предлагается хранить полную структуру (схему) в каждой ревизии
Да, как часть инсталляции. Ведь всегда из любой ревизии можно поставить полноценную версию без имения старой, так? Так почему бы не получить DESCRIBE от базы данных и не сравнить с той, что запихана в инсталляционном конфиге?

Но этот дифф на sql дампы нужно писать. Ну да, а что в наше время что-то пишут только негры? :) Хотя не исключено, что где-то есть уже такое, надо просто поискать.

(1)По-вашему надо запустить анализатор сравнения старой структуры и новой, только что приехавшей ((2)кстати новая структура приехав из svn ведь затрет старый файл) и внести изменения в БД.
1) Ага, а в чем проблема? В случае с миграцией ничего запускать не надо, оно само волшебным образом в базу внесется? :)
2) Ну да, затрет, а он нам и не нужен, так как вся нужная информация будет в новом, а текущую возьмем сразу у самой базы.

Опять же Вася вынул апдейт и может посмотреть что в базе поменялось -- глядя в файл миграции. А в Вашем случае ему нужно будет это вычислить.
Не знаю, как для этого несчастного Васи, а мне не составляет труда глазами определить разницу между двумя строками и то только в том случае, если она мне нужна :)
:)

Ну что же. Я понял Ваш способ. Спасибо.

Остальное наверное лирика -- "There's more than one way to do it." (TIMTOWTDI, usually pronounced 'Tim Toady')

а можно на "ты" с Вами общаться?
"There's more than one way to do it."
Я верю в искусственный разум, поэтому в техническом плане будут единственно верные решения :)

а можно на "ты" с Вами общаться?
Конечно! Так удобнее ;)

Re: Калька с Рубина.

Вот интересно, а почему считается, что меняется только схема базы? Вот у нас довольно часто меняются сами дефолтные данные, и тут сравнением структур не обойдешься.

Re: Калька с Рубина.

Пардон, какие "дефолтные данные"?

Re: Калька с Рубина.

А это уж от приложения зависит. А у вас что, сразу после инсталляции база пустая?

Re: Калька с Рубина.

Нет конечно, но при чем тут инсталляция? Дефолтные данные должны быть по дефолту, а в рабочей версии они уже не дефолтные.

Re: Калька с Рубина.

рабочая версия для девелоперов у нас в конторе как раз та, с которой впоследствии снимают дефолтные данные, так что это я по привычке =)

Но, как я и писал ниже, при апдейте порой модифицируется не только структура, но и данные, причем иногда необратимо. Тут никакой дифф не спасает (впрочем и миграции тоже).
Т.е. вместо хранения изменений вы их будете вычислять на лету. Да можно. Но этот дифф на sql дампы нужно писать. А для приведенного механизма его писать не надо :) Или он есть где-то готовый?

В книге по пиру упоминается, что PEAR_MDB2 может по двум xml'ным схемам вычислить запросы, необходимые для миграции с одной схемы на другую. Не знаю, сам не пробовал. Сами используем именно миграции, но завязанные на версии пакетов (у нас пировский инсталлятор встроенный).
Ну основании схемы не всегда можно построить правильную миграцию. Вот у меня в моем фреймворке, который стыдно показать (тм), есть скаффолдинг, который генерит объекты и шаблоны на основе схемы. Если таблица уже есть, то при включенной опции "обновлять таблицу" скрипт попытается внести нужные изменения в БД и кроме того автоматом дописать в текущую миграцию.

Однако если я хочу поле переименовать получается беда -- глупая машина считает, что старое поле надо убить, а новое создать. Ну и данные теряются. Понятно, что в таком случае миграцию нужно писать руками.

Наверняка еще что-нибудь может вылезти при автоматическом дифе.
Для частных случаев можно и ручками команду прописать. В смысле хранить в конфиге старые имена, если уж кто-то так наложал с именем таблицы/поля :) И это должно нормально работать, если конечно не надо делать login=password, password=login :)

Вообще такие случае действительно очень редки.

Re: Калька с Рубина.

Ну к сожалению водопадный подход в нынешней вебразработке -- это миф.

Копировать у мегавани, опять же пока мегаваня с тобой в одной комнате.

Я вот работал в паре с уделенным девелопером. После того как мы стали использовать миграции жизнь стала лучше.

Назад я не откатывал :) как и в svn. Однако приятно думать, что это сделать легко.

Re: Калька с Рубина.

Базу назад откатывать - это надо быть очень храбрым. Скажем сценарий: миграция добавила колонку в таблицу, приложение некоторое время работало с этой схемой и натолкало в эту колонку данных. Что делать с данными при откате? Выкинуть - жалко =)

Да и вообще не всегда можно назад откатиться: вот например в версии 1.0 хранили пароли пользователей по глупости открытым текстом. В версии 1.0.1 переделали на хранение хеша, сделали миграцию:
  update users set password=sha1(password);

Такое назад уже не откатишь.
Эрвин-Модульмарт позволяет тебе формировать код для изменения базы, только он сцуко безумно глючный. И у него плохо с хранимыми процедурами.
В любом случае, инструмент нужен, когда база данных большая (десятки и сотни таблиц). Для крошечных баз, ИМХО, можно и руками курочить.