Repeatable migrations в Flyway

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

Что такое Repeatable migrations (RM)?


Это миграции, обозначенные особым префиксом R__. Файл с запросами будет бежать в одном из случаев:

  1. Миграция еще не была выполнена успешно
  2. Хэш-сумма миграции изменилась
  3. Файл был переименован


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

В таблице состояния Flyway будет записано каждое изменение хэш суммы:

08-ddb7prm3jtwcytedk3c3pggu.png

Что писать в RM


Functions, stored procedures, views и захардкоженные данные. А в последствии изменять их в этих же файлах. Мой подход — по возможности разделить хранимые в бд объекты по типу + один файл на данные. Итого R__create_functions.sql, R__create_stored_procedures.sql, R__create_views.sql, R__data.sql. Трудности начнутся, если изменения в этих файлах зависимы одни на другие. В этом случаи переименовывайте, и разделяйте заново.

Один нюанс — все запросы в RM должны быть идемпотентны. Это одно из немногих мест, где идемпотентные миграции допустимы:

-- захардкоженные данные в БД, на которые лень писать админку
INSERT INTO country (country_id, country)
VALUES (1, 'USA'),
        (2, 'Ukraine'),
        (3, 'Belarus')
ON CONFLICT (country_id) DO NOTHING;


Триггеры и индексы лучше оставить в том месте, где создана\изменена таблица:

V005__create_table_film.sql
CREATE TABLE film
(
   film_id              INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
   title                TEXT                                                              NOT NULL,
   description          TEXT,
   release_year         year,
   language_id          SMALLINT REFERENCES language ON UPDATE CASCADE ON DELETE RESTRICT NOT NULL,
   original_language_id SMALLINT REFERENCES language ON UPDATE CASCADE ON DELETE RESTRICT,
   rental_duration      SMALLINT      DEFAULT 3                                           NOT NULL,
   rental_rate          NUMERIC(4, 2) DEFAULT 4.99                                        NOT NULL,
   length               SMALLINT,
   replacement_cost     NUMERIC(5, 2) DEFAULT 19.99                                       NOT NULL,
   rating               mpaa_rating   DEFAULT 'G',
   special_features     feature[],
   last_update          TIMESTAMPTZ   DEFAULT now()                                       NOT NULL,
   fulltext             TSVECTOR                                                          NOT NULL
);

CREATE TRIGGER film_fulltext_trigger
   BEFORE INSERT OR UPDATE
   ON film
   FOR EACH ROW
EXECUTE PROCEDURE tsvector_update_trigger('fulltext', 'pg_catalog.english', 'title', 'description');

CREATE INDEX ON film (fulltext);
CREATE INDEX ON film (title);
CREATE INDEX ON film (language_id);
CREATE INDEX ON film (original_language_id);


Вот так у вас будет выглядеть diff в гите:

dyygc9xhl8emsev8qpij-ilcg-4.png

Ссылка на гитхаб.
Официальная дока.

© Habrahabr.ru