Введение
merql — это библиотека на PHP для слияния трёх состояний базы данных. Она создаёт снимки таблиц, вычисляет наборы изменений между ними, объединяет два набора изменений относительно общей базы и выявляет конфликты на уровне столбцов. В ней используется тот же алгоритм, что git применяет к исходным файлам, но к строкам и столбцам, а не к строкам текста.
Без внешних инструментов. Поддерживает MySQL и SQLite благодаря подключаемым адаптерам соединения и драйверам диалектов.
Быстрый пример
Для слияния нужны три состояния одних и тех же данных: базовое, от которого начинались обе копии, и две копии после независимых изменений. Здесь песочница была создана из рабочей базы данных, и с тех пор обе базы изменились.
use Merql\Connection;
use Merql\Merql;
$live = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');
// Base: live at the moment the sandbox was copied from it.
Merql::init($live);
Merql::snapshot('base');
// ... copy live.sqlite to sandbox.sqlite, then both databases change ...
// Ours: the sandbox with our edits. Theirs: live with its own changes.
Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');
$result = Merql::merge('base', 'ours', 'theirs');
if ($result->isClean()) {
foreach ($result->operations() as $operation) {
echo "{$operation->type} {$operation->table} #{$operation->rowKey} ({$operation->source})\n";
}
} else {
echo "Conflicts: {$result->conflictCount()}\n";
}
Каждый снимок создаётся из базы данных, в которой хранится соответствующее состояние. Три снимка из одной базы, сделанные подряд, не описывают две разошедшиеся копии: theirs уже будет содержать изменения ours, и каждая добавленная ours строка будет отмечена как конфликт insert_insert.
В результате перечислены операции, которые преобразуют base в итоговое состояние и включают изменения обеих сторон; у каждой указано происхождение (ours, theirs или merged). Чтобы записать изменения песочницы в рабочую базу, примените операции, которых в ней ещё нет, с помощью Применение с проверкой.
Или из CLI, который подключается к базе данных, указанной в MERQL_DB_DSN для каждой команды snapshot:
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
# ... copy live.sqlite to sandbox.sqlite, then both databases change ...
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs
vendor/bin/merql merge base ours theirs --dry-run
Почему merql
Большинство инструментов сравнения состояний баз данных работают на уровне строк: если между двумя версиями различается хотя бы один столбец строки, вся строка отмечается как конфликт. Это приводит к ложным конфликтам, когда два человека редактируют разные столбцы одной строки.
merql выполняет слияние на уровне столбцов. Если одна сторона изменила title, а другая — status, эти изменения объединяются без конфликта. Конфликт возникает, только когда обе стороны изменили один и тот же столбец на разные значения.
Для столбцов TEXT и JSON merql может углубить слияние до уровня ячеек. Два изменения разных строк текстового столбца или разных ключей столбца JSON можно объединить тем же алгоритмом слияния трёх ветвей. Такое слияние нужно включить явно: передайте CellMergeConfig в ThreeWayMerge. Фасад Merql и компонент слияния CLI работают только на уровне столбцов. См. раздел Слияние на уровне ячеек.
Модель слияния
Base (snapshot at fork time)
/ \
Ours (one copy's changes) Theirs (the other copy's changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
Три состояния, как в git. Base — общий предок. Ours и theirs — две разошедшиеся копии, каждая из своей базы данных. Правила слияния одинаковы для обеих сторон. Эти имена используются в source каждой операции, в типах конфликтов (update_delete означает, что ours обновил данные, а theirs удалил их), а также в политиках разрешения OursWins и TheirsWins. Без общей базы невозможно отличить «добавлено» от «не изменено».
Слияние само по себе ничего не записывает. Его операции преобразуют base в итоговое состояние, поэтому Merql::apply() корректно выполняет их все, только если база данных всё ещё содержит base. Чтобы перенести изменения одной стороны в базу данных, уже содержащую изменения другой (например, из песочницы в рабочую базу), примените только недостающие операции; см. раздел Применение с проверкой.
Требования
- PHP 8.2 или более поздней версии.
ext-mbstringиext-zlib, необходимые зависимостиpitmaster/pitmasterдля сравнения текста.- Расширение базы данных для выбранного адаптера:
ext-pdoсext-pdo_sqliteилиext-pdo_mysqlлибоext-mysqli. Пакетcomposer.jsonдля merql лишь рекомендует установитьext-pdoиext-mysqli, поэтому Composer не проверяет их наличие. - База данных MySQL или SQLite. Для других движков нужен собственный драйвер.
Чем merql не является
merql — не инструмент миграции схемы. Он не реплицирует базы данных и не синхронизирует их в реальном времени. Он решает конкретную задачу: по трём состояниям базы данных сформировать четвёртое, объединённое, с указанием конфликтов.
Документация
- Начало работы — установка, создание первого снимка и первое слияние.
- CLIСправочник — полный справочник по команде
vendor/bin/merql. - PHP APIСправочник — полная API документация по всем классам.
- Планы слияния — стабильные идентификаторы операций, группы изменений и выбор.
- Применение с проверкой — выборочная запись с проверкой предшествующего состояния строк в рабочей базе.
- Откат — артефакты отката и проверка расхождений.