
# CLI リファレンス

merqlには、スナップショットの取得、差分の計算、マージの実行をターミナルから直接行うためのCLIが付属しています。Composerでインストールすると`vendor/bin/merql`として使え、merqlリポジトリのチェックアウトでは`bin/merql`です。

`vendor/bin/merql`は、merql 0.4.1以降では依存パッケージとしてのインストールからも実行できます。それ以前のリリースでは、パッケージ自身の`vendor/`ディレクトリ内でしかComposerオートローダーを探さなかったため、merqlを依存パッケージとしてインストールすると失敗していました。

## クイックスタート

```bash
# Capture a baseline from your database.
export MERQL_DB_DSN="sqlite:/path/to/db.sqlite"
vendor/bin/merql snapshot base

# Make some changes, then capture again.
vendor/bin/merql snapshot current

# See what changed.
vendor/bin/merql diff base current
```

3方向マージでは、baseから分岐した2つのデータベースからoursとtheirsを取得する必要があります。各スナップショットで`MERQL_DB_DSN`が参照するデータベースが正しいことを確認してください:

```bash
# Base: live before the sandbox was copied from it.
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

# Three-way merge with dry-run preview.
vendor/bin/merql merge base ours theirs --dry-run
```

3つのスナップショットをすべて同じデータベースから順番に取得しても、マージには使えません。theirsにはすでにoursの変更が含まれるため、oursが挿入した行はすべて`insert_insert`競合として報告されます。

## 環境変数

CLIは環境変数からデータベース接続情報を読み取ります。PDO DSN文字列または個別のMySQLパラメーターを使って接続できます。

### DSNによる接続

PDOを使った接続には`MERQL_DB_DSN`を使用します。SQLiteや標準外の構成ではこの方法が推奨されます。DSNではMySQLまたはSQLiteデータベースを指定する必要があります。CLIには他のエンジン用ドライバーを登録する方法がないためです。

| 変数 | 説明 | 例 |
|---|---|---|
| `MERQL_DB_DSN` | 完全なPDO DSN文字列 | `sqlite:/path/to/db.sqlite` |
| `MERQL_DB_USER` | ユーザー名（DSN指定時は省略可） | `root` |
| `MERQL_DB_PASS` | パスワード（DSN指定時は省略可） | `secret` |

```bash
# SQLite file.
export MERQL_DB_DSN="sqlite:/var/data/myapp.sqlite"

# MySQL via DSN.
export MERQL_DB_DSN="mysql:host=127.0.0.1;dbname=mydb;charset=utf8mb4"
export MERQL_DB_USER="root"
export MERQL_DB_PASS="secret"
```

ファイルベースのSQLiteデータベースを使います。各CLIコマンドは独立したプロセスで実行されるため、`sqlite::memory:`は常に新しい空のデータベースを開き、そのスナップショットにはテーブルが含まれません。

### MySQLの簡易指定

`MERQL_DB_DSN`が設定されていない場合、CLIはMySQL固有の変数を使います。

| 変数 | 既定値 | 説明 |
|---|---|---|
| `MERQL_DB_NAME` | （必須） | データベース名 |
| `MERQL_DB_HOST` | `127.0.0.1` | MySQLホスト |
| `MERQL_DB_PORT` | `3306` | MySQLポート |
| `MERQL_DB_USER` | `root` | MySQLユーザー名 |
| `MERQL_DB_PASS` | （空） | MySQLパスワード |
| `MERQL_DB_ADAPTER` | `pdo` | mysqliアダプターを使うには`mysqli`を設定 |

```bash
export MERQL_DB_NAME="wordpress"
export MERQL_DB_HOST="127.0.0.1"
export MERQL_DB_PORT="3306"
export MERQL_DB_USER="root"
export MERQL_DB_PASS="password"
```

## コマンド

### `merql snapshot <name>`

環境変数で指定されたデータベースの現在の状態を取得し、名前付きスナップショットとして保存します。データベースに接続するのはこのコマンドだけです。

```bash
vendor/bin/merql snapshot baseline
```

出力:

```
Snapshot 'baseline' saved
Tables: posts, comments, users, settings
```

スナップショットは、作業ディレクトリ配下の`.merql/snapshots/`にJSONファイルとして保存されます（[スナップショットの保存](#snapshot-storage)を参照）。名前に使用できるのは英数字、ハイフン、アンダースコアのみです。

一般的な手順は、変更前にスナップショットを取得し、変更後にもう一度取得することです。この2つのスナップショットを差分計算やマージの基礎として使います。

### `merql diff <base> <current>`

名前を指定した2つのスナップショットを比較し、差分の概要を表示します。

```bash
vendor/bin/merql diff baseline current
```

出力:

```
Changeset: baseline -> current
  Inserts: 3
  Updates: 7
  Deletes: 1
```

両方のスナップショットがすでに存在している必要があります（`merql snapshot`で事前に取得）。差分計算は保存済みのスナップショットファイルを使うため、データベース接続は不要です。

### `merql merge <base> <ours> <theirs>`

名前を指定した3つのスナップショットを3方向マージします。

```bash
vendor/bin/merql merge base ours theirs
```

出力:

```
Merge: base + ours + theirs
  Operations: 12
  Conflicts:  0
  Clean:      yes
```

3つのスナップショットがすべてすでに存在している必要があります。マージでは保存済みのスナップショットファイルを使うため、データベース接続は不要です。また、データベースへの書き込みは行いません。CLIには適用コマンドがありません。マージ結果を適用するにはPHPAPIを使ってください。

CLIは列単位でマージします。TEXT列やJSON列にはセル単位のマージを適用しないため、同じ列に対する異なる編集は常に競合になります（[#7](https://github.com/inline0/merql/issues/7)）。セル単位でマージするには、PHP内の`CellMergeConfig`を使って`ThreeWayMerge`を実行してください。

#### `--dry-run`

`--dry-run`フラグを追加すると、何も実行せずに、マージで生成されるSQL文をプレビューできます。各文はbaseの状態をマージ結果にする方法を示すため、両側の変更が含まれます。

```bash
vendor/bin/merql merge base ours theirs --dry-run
```

出力:

```
Merge: base + ours + theirs
  Operations: 3
  Conflicts:  0
  Clean:      yes

Dry run SQL:
  INSERT INTO `posts` (`id`, `title`, `status`) VALUES ('10', 'New Post', 'draft');
  UPDATE `posts` SET `title` = 'Updated Title' WHERE `id` = '5';
  DELETE FROM `comments` WHERE `id` = '3';
```

## 開発コマンド

merqlリポジトリをチェックアウトすると、オラクル駆動のテストスイート用スクリプトが`bin/`に追加されています。これらはComposerパッケージには含まれないため、依存パッケージとしてのインストールで使用できるのは`vendor/bin/merql`だけです。リポジトリのルートで`composer install`を実行してから、スクリプトを実行してください。

| コマンド | 説明 |
|---|---|
| `./bin/oracle <name>` | シナリオのマージ結果の期待値を計算 |
| `./bin/actual <name>` | シナリオに対してmerqlを実行し、出力を取得 |
| `./bin/compare <name>` | オラクルと実際の出力を差分比較 |
| `./bin/test-scenario <name>` | セットアップ、オラクル、実行、比較を行う全工程 |
| `./bin/test-regression` | すべてのシナリオを実行 |
| `./bin/test-regression --category clean` | 次のいずれかのカテゴリを実行: `clean`、`conflict`、`identity`、`types`、`scale`、`edge` |
| `./bin/test-regression --fast` | レポートなしで合否のみ表示 |
| `./bin/verify-compliance` | 完全な準拠レポート |
| `./bin/verify-all` | PHPStan + コードスタイル + PHPUnit + オラクル回帰テスト |

`bin/test-regression` は `--jobs` も受け付けますが、現在は値を無視し、シナリオを順番に実行します（[#5](https://github.com/inline0/merql/issues/5)）。不明な `--category` を指定すると、`No scenarios found` を出力して正常終了します。

シナリオの構成については、[Testing Strategy](https://merql.inline0.dev/ja/docs/advanced/testing)を参照してください。

## 終了ステータス

引数が不足している場合、`merql snapshot`、`diff`、`merge` は終了ステータス1で終了し、接続が設定されていない場合は `snapshot` も同様に終了します。スナップショットが見つからない場合、コマンドは捕捉されていない `SnapshotException` で終了し、ステータスはゼロ以外になります。不明なコマンドを指定すると `Unknown command` を出力しますが、現在はステータス0で終了します（[#3](https://github.com/inline0/merql/issues/3)）。

## スナップショットの保存場所

スナップショットは、作業ディレクトリを基準とする `.merql/snapshots/` に保存されます。各スナップショットは単一の JSON ファイルで、名前は `<name>.json` です。各コマンドが互いのスナップショットを見つけられるよう、すべてのコマンドを同じディレクトリから実行してください。

CLI には、別の保存場所を指定するオプションも環境変数もありません（[#8](https://github.com/inline0/merql/issues/8)）。別の場所を使うには、実行前に異なる作業ディレクトリへ移動してください。PHP では、スナップショットの取得または読み込みの前にディレクトリを設定します:

```php
use Merql\Snapshot\SnapshotStore;

SnapshotStore::setDirectory('/var/data/merql-snapshots');
```

スナップショットファイルは持ち運びできます。マシン間でコピーしたり、バージョン管理にコミットしたり、共同デバッグのために共有したりできます。CLI で取得したスナップショットは、両方が同じディレクトリを使っていれば、PHP で `SnapshotStore::load()` を使って読み込めます。その逆も可能です。
