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

Слияние на уровне столбцов

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

Проблема слияния на уровне строк

Представьте таблицу posts, в которой два человека редактируют одну и ту же строку:

Код
Base:    post #42  { title: "Hello",     content: "Body",    status: "draft"   }
Ours:    post #42  { title: "Hello",     content: "Body v2", status: "draft"   }
Theirs:  post #42  { title: "New Title", content: "Body",    status: "publish" }

При слиянии на уровне строк обнаруживается, что обе стороны изменили строку, и объявляется конфликт. Но изменения никак не пересекаются. Мы изменили content, а они — title и status.

Как слияние на уровне столбцов решает эту проблему

merql применяет одинаковые правила трехстороннего слияния к каждому столбцу отдельно:

Код
title:   base="Hello", ours="Hello",  theirs="New Title"  -> accept theirs
content: base="Body",  ours="Body v2", theirs="Body"      -> accept ours
status:  base="draft", ours="draft",  theirs="publish"    -> accept theirs

Результат: { title: "New Title", content: "Body v2", status: "publish" }. Слияние прошло без конфликтов.

Как это работает

Когда ThreeWayMerge обнаруживает, что обе стороны обновили одну и ту же строку, он передает обработку ColumnMerge::merge():

PHP
use Merql\Merge\ColumnMerge;

$result = ColumnMerge::merge(
    table: 'posts',
    rowKey: '42',
    base: ['title' => 'Hello', 'content' => 'Body', 'status' => 'draft'],
    ours: ['title' => 'Hello', 'content' => 'Body v2', 'status' => 'draft'],
    theirs: ['title' => 'New Title', 'content' => 'Body', 'status' => 'publish'],
);

// $result['values']    -> merged column values
// $result['conflicts'] -> list of Conflict objects (empty if clean)

Для каждого столбца действуют те же правила:

База Наша сторона Их сторона Результат
A A A Оставить базовое значение (без изменений)
A A B Принять значение их стороны
A B A Принять наше значение
A B B Принять (обе стороны согласны)
A B C Конфликт (разные значения)

Когда конфликты все же возникают

Конфликт на уровне столбца возникает, когда обе стороны изменили один и тот же столбец на разные значения:

Код
Base:    post #42  { title: "Hello" }
Ours:    post #42  { title: "Welcome" }
Theirs:  post #42  { title: "Greetings" }

Обе стороны изменили title на разные значения. Это настоящий конфликт, требующий разрешения.

PHP
$result = ColumnMerge::merge(
    'posts', '42',
    ['title' => 'Hello'],
    ['title' => 'Welcome'],
    ['title' => 'Greetings'],
);

// $result['conflicts'] contains one Conflict:
//   table: "posts", column: "title"
//   oursValue: "Welcome", theirsValue: "Greetings", baseValue: "Hello"

Частичные конфликты

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

Код
Base:    { title: "Hello", content: "Body",    status: "draft" }
Ours:    { title: "Welcome", content: "Body v2", status: "draft" }
Theirs:  { title: "Greetings", content: "Body",    status: "publish" }

Здесь возникает конфликт для title (обе стороны изменили его по-разному), content объединяется без конфликта (изменено только нами), а status — без конфликта (изменено только ими). Операция содержит:

PHP
// values: { title: "Welcome", content: "Body v2", status: "publish" }
// One conflict on the "title" column.

Сравнение значений

Значения столбцов сравниваются как строки после приведения типов. NULL считается отдельным значением:

  • NULL → NULL = без изменений.
  • NULL → "hello" = обновление.
  • "hello" → NULL = обновление.
  • "1" → 1 = без изменений (оба значения приводятся к строке "1").

Это соответствует семантике получения данных адаптером MerQL: все значения базы данных возвращаются строками (или NULL).

Слияние на уровне ячеек

Когда слияние на уровне столбцов все еще приводит к конфликту (обе стороны изменили один и тот же столбец на разные значения), merql может углубить обработку и выполнить слияние на уровне ячеек. Это применяется к столбцам TEXT (построчное сравнение) и JSON (сравнение по ключам) и позволяет разрешать случаи, когда обе стороны изменили разные части значения одного и того же столбца.