
# はじめに

merqlは、データベースの状態を三者マージする純粋な PHP ライブラリです。テーブルのスナップショットを取得し、スナップショット間の変更セットを計算し、共通のbaseに対して2組の変更をマージして、列単位で競合を検出します。ソースファイルに対するgitと同じアルゴリズムを、行や列に適用します。

外部ツールは不要です。プラグイン可能な接続アダプターと方言ドライバーを通じて、MySQLとSQLiteをサポートします。

## 使用例

マージには同じデータの3つの状態が必要です。両方のコピーが出発点としたbaseと、それぞれ独立して変更された2つのコピーです。ここでは、サンドボックスを本番データベースからコピーし、その後、両方に編集を加えています。

```php
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";
}
```

各スナップショットは、その状態を保持するデータベースから取得します。1つのデータベースから3つすべてを順番に取得しても、分岐した2つのコピーを表せません。theirsにはすでにoursの変更が含まれるため、oursが挿入したすべての行が `insert_insert` の競合として報告されます。

結果には、baseをマージ後の状態に変換する両側の操作が一覧表示され、それぞれの出所（`ours`、`theirs`、`merged`）が示されます。サンドボックスの変更を本番環境に書き込むには、[Guarded Apply](https://merql.inline0.dev/ja/docs/apply/guarded)を使って、本番環境にまだない操作だけを適用します。

または CLI から実行できます。これは、各 `snapshot` の `MERQL_DB_DSN` で指定されたデータベースに接続します:

```bash
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を選ぶ理由

状態を比較するデータベースツールの多くは行単位で処理します。2つのバージョン間で行の列が1つでも異なると、行全体を競合として扱います。その結果、2人が同じ行の異なる列を編集した場合に、誤った競合が発生します。

merqlは列単位でマージします。一方が `title` を変更し、もう一方が `status` を変更した場合、競合せずにマージできます。両側が同じ列を異なる値に変更した場合にのみ、競合が発生します。

TEXTと JSON の列では、セル単位のマージを使ってさらに細かく処理できます。テキスト列の異なる行、または JSON 列の異なるキーに対する2つの編集は、同じ三者マージアルゴリズムを使って競合せずにマージできます。セル単位のマージはオプトインです。`ThreeWayMerge` に `CellMergeConfig` を渡してください。`Merql` ファサードと CLI は列単位でのみマージします。[Cell-Level Merge](https://merql.inline0.dev/ja/docs/merge/cell-level)を参照してください。

## マージモデル

```
         Base (snapshot at fork time)
        /                              \
   Ours (one copy's changes)            Theirs (the other copy's changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)
```

gitと同じ3つの状態を使います。baseは共通の祖先です。oursとtheirsは分岐後の2つのコピーで、それぞれのデータベースから取得します。マージ規則は両側を同等に扱います。これらの名前は、各操作の `source`、競合の種類（`update_delete` はoursが更新し、theirsが削除したことを意味します）、`OursWins` と `TheirsWins` の解決ポリシーに使われます。共通のbaseがなければ、「追加」と「変更なし」を区別できません。

マージ自体は何も書き込みません。マージ操作はbaseをマージ後の状態に変換するため、`Merql::apply()` がすべての操作を問題なく実行できるのは、baseの状態を保持するデータベースに対してのみです。サンドボックスの編集を本番環境に反映するなど、すでに片側の変更が入っているデータベースにもう一方の変更を取り込むには、まだ適用されていない操作だけを適用します。[Guarded Apply](https://merql.inline0.dev/ja/docs/apply/guarded)を参照してください。

## 必要条件

- PHP 8.2以降。
- テキスト差分を提供する `pitmaster/pitmaster` の依存関係として必要な `ext-mbstring` と `ext-zlib`。
- 使用するアダプターに対応したデータベース拡張機能: `ext-pdo` と `ext-pdo_sqlite` または `ext-pdo_mysql`、あるいは `ext-mysqli`。merqlの `composer.json` は `ext-pdo` と `ext-mysqli` を推奨するだけなので、Composerはこれらの有無を確認しません。
- MySQLまたはSQLiteのデータベース。その他のエンジンにはカスタム[driver](https://merql.inline0.dev/ja/docs/apply/drivers)が必要です。

## merqlでできないこと

merqlはスキーマ移行ツールではありません。データベースを複製したり、リアルタイムで同期したりするものでもありません。特定の問題を解決するツールです。3つのデータベース状態を受け取り、競合を特定したうえで、マージ後の4つ目の状態を生成します。

## ドキュメント

- [Getting Started](https://merql.inline0.dev/ja/docs/getting-started) -- インストール、最初のスナップショット、最初のマージ。
- [CLI Reference](https://merql.inline0.dev/ja/docs/cli) -- `vendor/bin/merql` コマンドの全リファレンス。
- [PHP API Reference](https://merql.inline0.dev/ja/docs/api) -- すべてのクラスの完全な API ドキュメント。
- [Merge Plans](https://merql.inline0.dev/ja/docs/merge/plans) -- 安定した操作ID、変更グループ、選択。
- [Guarded Apply](https://merql.inline0.dev/ja/docs/apply/guarded) -- 本番行の事前条件を使った選択的な書き込み。
- [Rollback](https://merql.inline0.dev/ja/docs/apply/rollback) -- ロールバック用アーティファクトと差異チェック。
