Перейти к содержимому

Планы слияния

MergeResult — исполняемый результат слияния. MergePlan — его форма, пригодная для проверки. Используйте его, если приложению нужно показать, подготовить, сериализовать или выборочно применить изменения базы данных перед записью в целевую базу данных.

PHP
use Merql\Merql;

$plan = Merql::plan(
    id: 'plan-2026-06-06',
    base: 'base',
    ours: 'ours',
    theirs: 'theirs',
    metadata: ['sandbox_id' => 'ow_123'],
);

Merql::plan() загружает три именованных снимка и объединяет их на уровне столбцов, как Merql::merge(). Чтобы спланировать слияние с использованием слияния на уровне ячеек для столбцов TEXT и JSON, самостоятельно создайте результат и передайте его в MergePlanBuilder:

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;

$base = SnapshotStore::load('base');
$ours = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);
$plan = (new MergePlanBuilder())->build('plan-2026-06-06', $base, $ours, $theirs, $result);

План содержит:

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

Стабильные идентификаторы

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

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

PHP
foreach ($plan->changeGroups as $group) {
    echo $group->id . ': ' . implode(', ', $group->operationIds);
}

Структура операции

Каждый MergePlanOperation содержит достаточно контекста для проверки и отката.

PHP
$operation = $plan->operations[0];

$operation->id;              // stable operation ID
$operation->type;            // insert, update, delete
$operation->table;           // canonical table name
$operation->rowKey;          // encoded identity key
$operation->identityColumns; // columns used for identity
$operation->identityValues;  // decoded identity values
$operation->source;          // ours, theirs, or merged
$operation->baseRow;         // row in base snapshot, or null
$operation->oursRow;         // row in ours snapshot, or null
$operation->theirsRow;       // row in theirs snapshot, or null
$operation->resultValues;    // values the merge would write
$operation->changedColumns;  // changed result columns

Выбор операций

Используйте ChangeGroupSelection, когда UI подготавливает группы. Преобразуйте его в OperationSelection при применении изменений или создании артефактов отката.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

$selection = ChangeGroupSelection::fromIds([
    $plan->changeGroups[0]->id,
]);

$operationSelection = $selection->toOperationSelection($plan);

$selectedResult = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    $operationSelection,
    SnapshotStore::load($plan->baseSnapshot),
);

Выбранный результат — это чистый MergeResult, содержащий только выбранные операции. Его можно передать в Merql::apply() или Merql::applyGuarded().

Выбор отсутствующих в целевой базе данных изменений

План содержит операции обеих сторон, поскольку результат слияния преобразует базу в объединенное состояние. Если в целевой базе уже есть состояние одной из сторон, выберите только те операции, которых в ней еще нет. Для целевой базы с состоянием их стороны, например рабочей базы данных, в которую передаются изменения из тестовой копии, это все операции с источником ours, а также обновления merged, объединяющие изменения столбцов обеих сторон:

PHP
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

// Rows with an unresolved conflict stay out of the selection.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
    $conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}

$ids = [];
foreach ($plan->operations as $operation) {
    $missing = $operation->source === 'ours'
        || ($operation->source === 'merged' && $operation->type === 'update');

    if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
        $ids[] = $operation->id;
    }
}

$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    OperationSelection::fromIds($ids),
    SnapshotStore::load($plan->baseSnapshot),
);

Операции с источником theirs, а также удаления merged (строки, удаленные обеими сторонами) уже присутствуют в целевой базе.

План, построенный на основе слияния с конфликтами, по-прежнему содержит операцию для каждой конфликтующей строки, обновленной обеими сторонами. В результате для конфликтующего столбца сохраняется наше значение, а SelectedMergeResultFactory возвращает чистый результат для любого набора выбранных операций. И у операции, и у группы изменений значение conflictState даже в этом случае равно clean (#9). Поэтому самостоятельно проверьте $plan->conflicts, как описано выше, или разрешите конфликты с помощью ConflictResolver и создайте план на основе разрешенного результата с помощью MergePlanBuilder. См. раздел Защищенное применение, посвященный записи выбранных операций с предварительными условиями для актуальных строк.

Сериализация

Планы слияния и выборки имеют стабильную сериализацию JSON. Сохраните сериализованный план, если на следующем этапе применения нужно использовать точно тот же набор проверенных операций.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\MergePlanSerializer;

$json = MergePlanSerializer::toJson($plan);
$restored = MergePlanSerializer::fromJson($json);

$selectionJson = json_encode($selection->toArray(), JSON_THROW_ON_ERROR);
$selection = ChangeGroupSelection::fromArray(
    json_decode($selectionJson, true, flags: JSON_THROW_ON_ERROR),
);

Снимки с псевдонимами

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

PHP
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

SnapshotStore::save($snapshotter->captureAliased('live', [
    'wp_posts' => 'wp_posts',
]));

SnapshotStore::save($snapshotter->captureAliased('sandbox', [
    'wp_onumia_abc_posts' => 'wp_posts',
]));

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