
# PHP APIリファレンス

merqlのすべての公開クラスとメソッドの完全なリファレンスです。

## Merql（ファサード）

`Merql\Merql`は、一般的な操作のための静的エントリーポイントです。低レベルのクラスをラップし、スナップショットの永続化を自動で管理します。

```php
use Merql\Connection;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
use Merql\Merql;

$connection = Connection::sqlite(':memory:');
Merql::init($connection, identityRules: new IdentityRuleSet([
    'options' => IdentityRule::natural(['option_name']),
]));
```

### `init(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null): void`

データベース接続を使ってファサードを初期化します。`snapshot()`、`apply()`、`applyGuarded()`の前に呼び出す必要があります。データベースを切り替える場合（たとえば、異なるコピーからoursとtheirsをスナップショットする場合）は、再度呼び出してください。`$driver`が指定されていない場合、`DriverFactory::create()`が呼び出されます。ドライバーが登録されていないと、MySQLとSQLite以外のエンジンでは例外がスローされます。自動検出を上書きするにはドライバーを渡し、スナップショットのキャプチャ時にテーブルごとの行識別を上書きするには識別ルールセットを渡します。

### `snapshot(string $name, array $tables = []): Snapshot`

現在のデータベース状態をキャプチャし、ディスクに保存します。特定のテーブルをスナップショットするにはテーブル名の配列を渡し、すべてのテーブルを対象にするには省略します。

```php
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
```

### `diff(string $base, string $current): Changeset`

名前を指定した2つのスナップショットを読み込み、その間の変更セットを計算します。保存済みスナップショットを使うため、`init()`は必要ありません。

```php
$changeset = Merql::diff('before', 'after');
```

### `merge(string $base, string $ours, string $theirs): MergeResult`

名前を指定した3つのスナップショットを3方向にマージします。競合のない操作と競合を含む結果を返します。カラムレベルでマージします。ファサードは`CellMergeConfig`を指定せずに`ThreeWayMerge`を構築するため、セルレベルのマージは適用されません。セルレベルのマージには、[`ThreeWayMerge`](#threewaymerge)を直接使用してください。

```php
$result = Merql::merge('base', 'ours', 'theirs');
```

### `patch(string $base, string $changes): MergeResult`

2方向マージ：変更をbaseに適用します。`merge($base, $base, $changes)`と同等です。並行編集がないため、常に競合のない結果になります。`merge()`と同様にカラムレベルです。

```php
$result = Merql::patch('base', 'changes');
```

### `apply(MergeResult $result): ApplyResult`

`init()`に渡されたデータベースに、競合のないマージ結果を適用します。`init()`が必要です。未解決の競合が残っている場合は`ConflictException`をスローします。

結果内のすべての操作が実行されるため、`merge()`の結果全体を正常に適用できるのは、base状態を保持しているデータベースだけです。すでに片側の状態を保持しているデータベースには、不足している操作を選択して適用してください。[条件付き適用](https://merql.inline0.dev/ja/docs/apply/guarded)を参照してください。

```php
$applied = Merql::apply($result);
echo $applied->rowsAffected();
```

### `plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan`

名前を指定した3つのスナップショットから、UI対応のマージプランを作成します。プランには、安定した操作ID、変更グループID、操作コンテキスト、競合、スキーマの不一致、概要、メタデータ、プランハッシュが含まれます。`merge()`と同様にカラムレベルでマージします。セルレベルのマージには、セルマージ済みの結果を`MergePlanBuilder::build()`に渡してください。

```php
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
    'source' => 'review-ui',
]);
```

### `applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult`

ライブ行に対する楽観的な事前条件を使って、競合のないマージ結果を適用します。更新と削除では、現在のライブ行が`$expectedLive`と一致する必要があります。挿入では、対象の識別情報がまだ存在しないことが必要です。`$expectedLive`は対象データベース（oursを、theirsのスナップショット元のデータベースに反映する場合はtheirs）のスナップショットです。影響を受けた行が0件のSQL文があると、トランザクション全体がロールバックされます。

```php
use Merql\Snapshot\SnapshotStore;

$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
```

### `reset(): void`

内部の接続とSnapshotterのインスタンスをクリアします。主にテスト実行間の状態リセットに使用します。

---

## 接続

`Merql\Connection`は適切なデフォルト値で接続アダプターを構築します。PDO接続では、エラー時の例外、連想配列形式のフェッチ、エミュレートしないプリペアドステートメント、文字列化したフェッチを使用します。mysqliアダプターは、取得値を同じ文字列またはnullの形式に正規化します。

### `mysql(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): PdoDatabaseConnection`

```php
use Merql\Connection;

$connection = Connection::mysql('127.0.0.1', 'my_app', 'root', 'secret');
```

### `mysqli(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

```php
$connection = Connection::mysqli('127.0.0.1', 'my_app', 'root', 'secret');
```

### `sqlite(string $path = ':memory:'): PdoDatabaseConnection`

SQLite接続を作成します。`PRAGMA foreign_keys = ON`によって外部キーを自動的に有効にします。

```php
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
```

### `fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection`

任意のPDO DSN文字列を使って接続します。merqlには`mysql`と`sqlite`のドライバーのみが同梱されています。それ以外のDSNでも接続はできますが、ドライバーが`DriverFactory::register()`で登録されていない場合、`Merql::init()`、`Snapshotter`、適用処理は`Unsupported database driver`をスローします。

```php
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
```

### `fromPdo(PDO $pdo): PdoDatabaseConnection`

既存のPDOインスタンスをラップします。

### `fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

既存のmysqliインスタンスをラップします。

### アダプターの契約

すべてのアダプターは`Merql\Database\DatabaseConnection`を実装します。

```php
interface DatabaseConnection
{
    public function driverName(): string;
    public function execute(string $sql, array $params = []): int;
    public function query(string $sql, array $params = []): array;
    public function scalar(string $sql, array $params = []): ?string;
    public function beginTransaction(): void;
    public function commit(): void;
    public function rollBack(): void;
    public function lastInsertId(): string;
}
```

`execute()`、`query()`、`scalar()`は、`?`プレースホルダー用に位置指定`list<scalar|null>`パラメーターを受け付けます。`query()`は`list<array<string, scalar|null>>`を返します。値はPDOとmysqliの両アダプターで、同じ文字列またはnullの形式に正規化されます。

---

## スナップショットツール

`Merql\Snapshot\Snapshotter`はデータベースの状態をキャプチャします。ファサードとは異なり、フィルターを完全に制御でき、永続化は自動で行いません。

### `__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)`

ドライバーを指定しない場合、接続の識別情報から自動検出されます。テーブルごとの行識別でスキーマのデフォルトを上書きするには、`IdentityRuleSet`を渡してください。

### `capture(string $name, array $tables = [], array $filters = []): Snapshot`

現在の状態をキャプチャします。対象を絞るにはテーブル名を渡します。テーブル、カラム、行を除外するにはフィルターオブジェクトを渡します。

```php
use Merql\Snapshot\Snapshotter;
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Filter\RowFilter;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$snapshotter = new Snapshotter(
    $connection,
    identityRules: new IdentityRuleSet([
        'wp_options' => IdentityRule::natural(['option_name']),
    ]),
);

// All tables.
$snapshot = $snapshotter->capture('full');

// Specific tables with filters.
$snapshot = $snapshotter->capture('filtered', ['posts', 'users'], [
    TableFilter::exclude(['cache_*', 'sessions']),
    ColumnFilter::ignore(['updated_at', 'created_at']),
    RowFilter::create(fn(string $table, array $row) => $row['status'] !== 'trash'),
]);
```

### `captureAliased(string $name, array $physicalToCanonicalTableMap, array $filters = []): Snapshot`

物理テーブルを正規のテーブル名でキャプチャします。同じ論理テーブルの物理名が環境ごとに異なる場合に使用します。

```php
$snapshot = $snapshotter->captureAliased('sandbox', [
    'wp_onumia_abcd_posts' => 'wp_posts',
    'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
```

生成されたスナップショットは正規のテーブル名を保存しつつ、物理テーブルのスキーマ、設定された識別カラム、行キー、フィルターも保持します。

### `fromData(string $name, array $tableData): Snapshot`（静的）

データベース接続なしで、未加工データからスナップショットを作成します。テストや別のソースからデータをインポートする場合に便利です。

```php
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$schema = new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']);

$snapshot = Snapshotter::fromData('test', [
    'posts' => new TableSnapshotData($schema, [
        ['id' => '1', 'title' => 'Hello'],
        ['id' => '2', 'title' => 'World'],
    ], ['id']),
]);
```

---

## スナップショット

`Merql\Snapshot\Snapshot`は、データベースの完全な状態を表す読み取り専用の値オブジェクトです。

```php
$snapshot->name;                    // string: snapshot identifier
$snapshot->tables;                  // array<string, TableSnapshot>
$snapshot->tableNames();            // list<string>
$snapshot->hasTable('posts');       // bool
$snapshot->getTable('posts');       // ?TableSnapshot
```

---

## TableSnapshot

`Merql\Snapshot\TableSnapshot` は1つのテーブルの行、フィンガープリント、スキーマを保持します。

```php
$table = $snapshot->getTable('posts');

$table->schema;                    // TableSchema
$table->identityColumns;           // list<string>: columns used for row identity
$table->fingerprints;              // array<string, string>: row key to hash
$table->rows;                      // array<string, array<string, mixed>>

$table->rowKeys();                 // list<string>
$table->rowCount();                // int
$table->hasRow('42');              // bool
$table->getRow('42');              // ?array<string, mixed>
$table->getFingerprint('42');      // ?string
```

---

## TableSnapshotData

`Merql\Snapshot\TableSnapshotData` は `Snapshotter::fromData()` の入力データです。

```php
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$data = new TableSnapshotData(
    schema: new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']),
    rows: [
        ['id' => '1', 'title' => 'First Post'],
    ],
    identityColumns: ['id'],
);
```

---

## IdentityRule

`Merql\Identity\IdentityRule` は行キーの構築に使用する列を定義します。

```php
use Merql\Identity\IdentityRule;

$primary = IdentityRule::primary(['id']);
$natural = IdentityRule::natural(['option_name']);
$composite = IdentityRule::composite(['object_id', 'term_taxonomy_id']);
$content = IdentityRule::content(['name', 'value']);

$key = $natural->key(['option_name' => 'siteurl']);
// "siteurl"
```

### `forSchema(TableSchema $schema): IdentityRule`（静的）

スキーマからデフォルトの識別ルールを解決します。主キー、最初の一意キー、
続いてコンテンツへのフォールバックの順です。

### `toArray(): array`

ルールを `['type' => string, 'columns' => list<string>]` としてシリアライズします。

### `fromArray(array $data): IdentityRule`（静的）

シリアライズされたデータからルールを復元します。

---

## IdentityRuleSet

`Merql\Identity\IdentityRuleSet` はテーブル固有の識別ルールの上書きを保存します。

```php
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$rules = new IdentityRuleSet([
    'wp_options' => IdentityRule::natural(['option_name']),
]);

$rule = $rules->ruleFor($schema);
```

### `with(string $table, IdentityRule $rule): IdentityRuleSet`

テーブルの上書きを1つ追加した新しいルールセットを返します。

### `conflictsFor(string $table, IdentityRule $rule, array $rows): array`

同じルールで複数の行が同一キーになる場合、`IdentityConflict` のエントリーを返します。

### `toArray(): array`

テーブルルールを JSON で安全な配列としてシリアライズします。

### `fromArray(array $data): IdentityRuleSet`（静的）

シリアライズされたデータからルールセットを復元します。

---

## SnapshotStore

`Merql\Snapshot\SnapshotStore` はスナップショットを JSON ファイルとして保存し、読み込みます。

### `setDirectory(string $directory): void`

保存ディレクトリを設定します。デフォルトは `.merql/snapshots` です。

### `save(Snapshot $snapshot): void`

スナップショットをディスクに書き込みます。

### `load(string $name): Snapshot`

名前を指定してスナップショットを読み込みます。見つからない場合は `SnapshotException` をスローします。

### `exists(string $name): bool`

スナップショットファイルが存在するか確認します。

### `delete(string $name): void`

スナップショットファイルを削除します。

---

## 差分比較

`Merql\Diff\Differ` は2つのスナップショットを比較し、変更セットを生成します。

### `diff(Snapshot $base, Snapshot $current): Changeset`

```php
use Merql\Diff\Differ;

$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
```

### `valuesEqual(mixed $a, mixed $b): bool`（静的）

文字列に変換して2つの値を比較します。NULLは別個の値として扱われます。`NULL === NULL` はtrue、`NULL === ''` はfalseです。

---

## 変更セット

`Merql\Diff\Changeset` は行単位の操作をまとめた読み取り専用コレクションです。

```php
$changeset->inserts();             // list<RowInsert>
$changeset->updates();             // list<RowUpdate>
$changeset->deletes();             // list<RowDelete>
$changeset->isEmpty();             // bool
$changeset->count();               // int: total operations
$changeset->forTable('posts');     // Changeset: filtered to one table
```

---

## RowInsert, RowUpdate, RowDelete

個々の行変更を表す読み取り専用の値オブジェクトです。

### RowInsert

```php
$insert->table;                    // string
$insert->rowKey;                   // string
$insert->values;                   // array<string, mixed>
```

### RowUpdate

```php
$update->table;                    // string
$update->rowKey;                   // string
$update->columnDiffs;              // list<ColumnDiff>
$update->fullRow;                  // array<string, mixed>: complete current row
$update->columnDiffMap();          // array<string, ColumnDiff>: keyed by column name
```

### RowDelete

```php
$delete->table;                    // string
$delete->rowKey;                   // string
$delete->oldValues;                // array<string, mixed>: values before deletion
```

---

## ColumnDiff

`Merql\Diff\ColumnDiff` は行の更新における単一の列変更を表します。

```php
$diff->column;                     // string
$diff->oldValue;                   // mixed
$diff->newValue;                   // mixed
```

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` は中核となるマージアルゴリズムです。

### `__construct(?CellMergeConfig $cellMergeConfig = null)`

`CellMergeConfig` を渡すと、TEXT列と JSON 列でセル単位のマージが有効になります。

```php
use Merql\Merge\ThreeWayMerge;
use Merql\CellMerge\CellMergeConfig;

// Default: column-level merge only.
$merge = new ThreeWayMerge();

// With cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
```

### `merge(Snapshot $base, Snapshot $ours, Snapshot $theirs): MergeResult`

3方向マージを実行します。変更セット（ベースから自分側、ベースから相手側）を内部で計算し、マージします。

```php
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
```

### `patch(Snapshot $base, Snapshot $changes): MergeResult`

2方向マージの簡易版です。`merge($base, $base, $changes)` と同等です。

### `schemaMismatches(): array`

直近のマージで検出されたスキーマの差異（スナップショット間で追加または削除された列）について、`SchemaException[]` を返します。

---

## MergeResult

`Merql\Merge\MergeResult` はマージ結果を含む読み取り専用の値オブジェクトです。

```php
$result->isClean();                // bool: no conflicts
$result->operations();             // list<MergeOperation>
$result->conflicts();              // list<Conflict>
$result->operationCount();         // int
$result->conflictCount();          // int
$result->baseSnapshot();           // ?Snapshot
$result->schemaMismatches();       // list<SchemaException>
$result->hasSchemaMismatches();    // bool
```

---

## MergePlan

`Merql\Plan\MergePlan` はマージを確認するためのシリアライズ可能なモデルです。

```php
$plan->id;                        // string
$plan->baseSnapshot;              // string
$plan->oursSnapshot;              // string
$plan->theirsSnapshot;            // string
$plan->summary;                   // MergePlanSummary
$plan->changeGroups;              // list<MergePlanChangeGroup>
$plan->operations;                // list<MergePlanOperation>
$plan->conflicts;                 // list<MergePlanConflict>
$plan->schemaMismatches;          // list<string>
$plan->hash;                      // string
$plan->metadata;                  // array<string, mixed>
```

安定した JSON のラウンドトリップには、`MergePlanSerializer::toJson()` と `::fromJson()` を使用してください。

## 選択項目

`ChangeGroupSelection` はステージ済みの変更グループIDを保存します。選択したマージ結果やロールバック計画を作成するときに、`OperationSelection` に変換します。

```php
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;

$selection = ChangeGroupSelection::fromIds([$groupId]);
$operationSelection = $selection->toOperationSelection($plan);

$selected = (new SelectedMergeResultFactory())
    ->fromOperationSelection($plan, $operationSelection);
```

## RollbackPlan

`Merql\Rollback\RollbackPlan` は、選択された適用に対する逆操作とドリフトの予測値を保存します。

```php
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;

$rollback = (new RollbackPlanBuilder())->build(
    'rollback-1',
    $plan,
    $operationSelection,
    $liveBeforeRows,
);

$json = RollbackPlanSerializer::toJson($rollback);
```

ロールバックを適用する前に `RollbackDrift` を使用し、その後 `RollbackPlanApplier` で逆操作を実行します。

---

## MergeOperation

`Merql\Merge\MergeOperation` はマージで解決された単一の操作です。

```php
$op->type;                         // string: 'insert' | 'update' | 'delete'
$op->table;                        // string
$op->rowKey;                       // string
$op->values;                       // array<string, mixed>
$op->source;                       // string: 'ours' | 'theirs' | 'merged'
```

型定数：

```php
MergeOperation::TYPE_INSERT;       // 'insert'
MergeOperation::TYPE_UPDATE;       // 'update'
MergeOperation::TYPE_DELETE;       // 'delete'
```

---

## 競合

`Merql\Merge\Conflict` は未解決のマージ競合を表します。

```php
$conflict->table();                // string
$conflict->rowKey();               // string
$conflict->type();                 // string: 'update_update' | 'update_delete' | 'delete_update' | 'insert_insert'
$conflict->column();               // ?string (null for structural conflicts)
$conflict->oursValue();            // mixed
$conflict->theirsValue();          // mixed
$conflict->baseValue();            // mixed
$conflict->primaryKey(['id']);     // array<string, string>: decoded from rowKey
```

### 競合の種類

| 種類 | 意味 |
|---|---|
| `update_update` | 両側で同じ列が異なる値に変更された |
| `update_delete` | 相手側が削除した行を自分側で更新した |
| `delete_update` | 相手側が更新した行を自分側で削除した |
| `insert_insert` | 両側で同じ主キーを持つ行が挿入された |

---

## ConflictResolver

`Merql\Merge\ConflictResolver` はマージ結果内のすべての競合に解決ポリシーを適用します。

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult`（静的）

ポリシーに従って競合を解決した新しい `MergeResult` を返します。

```php
use Merql\Merge\ConflictResolver;
use Merql\Merge\ConflictPolicy;

// Accept all "theirs" values when conflicts arise.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Accept all "ours" values.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);

// Return unchanged (for manual inspection).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
```

---

## ConflictPolicy

`Merql\Merge\ConflictPolicy` は3つのケースを持つバックドenumです。

```php
use Merql\Merge\ConflictPolicy;

ConflictPolicy::OursWins;          // Resolve all conflicts in favor of "ours"
ConflictPolicy::TheirsWins;        // Resolve all conflicts in favor of "theirs"
ConflictPolicy::Manual;            // Leave conflicts unresolved for manual handling
```

---

## ColumnMerge

`Merql\Merge\ColumnMerge` は1行に対して列ごとのマージ処理を行います。内部で `ThreeWayMerge` に使用されます。

### `merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array`（静的）

`['values' => array, 'conflicts' => list<Conflict>]` を返します。

```php
use Merql\Merge\ColumnMerge;

$result = ColumnMerge::merge(
    'posts',
    '42',
    ['title' => 'Hello', 'status' => 'draft'],     // base
    ['title' => 'Hello', 'status' => 'published'],  // ours
    ['title' => 'New Title', 'status' => 'draft'],   // theirs
);

// $result['values'] = ['title' => 'New Title', 'status' => 'published']
// $result['conflicts'] = [] (clean merge)
```

---

## 適用機能

`Merql\Apply\Applier` はデータベースに対してマージ結果を実行します。

### `__construct(DatabaseConnection $connection, ?Driver $driver = null)`

### `apply(MergeResult $result, ?Snapshot $base = null): ApplyResult`

すべての操作を1つのトランザクション内で実行します。エラーが発生するとロールバックし、スローせずに `ApplyResult::errors()` でエラーを報告します。結果に未解決の競合がある場合は `ConflictException` をスローします。

```php
use Merql\Apply\Applier;

$applier = new Applier($connection);
$applied = $applier->apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
    print_r($applied->errors());
}
```

外部キーに基づく順序付けは自動で処理されます。挿入は依存関係の順に、削除は依存関係の逆順に並べられます。

---

## ApplyResult

`Merql\Apply\ApplyResult` はapplierが返す読み取り専用の値オブジェクトです。

```php
$applied->rowsAffected();         // int
$applied->errors();               // list<string>
$applied->hasErrors();            // bool
```

適用がロールバックされた後も、`rowsAffected()` には失敗前に実行されたステートメント数が含まれます（[#4](https://github.com/inline0/merql/issues/4)）。この件数を信頼する前に `hasErrors()` を確認してください。

---

## DryRun

`Merql\Apply\DryRun` は何も実行せずに、人が読める SQL を生成します。

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array`（静的）

値を埋め込んだ SQL 文字列のリストを返します（表示専用）。

```php
use Merql\Apply\DryRun;

$statements = DryRun::generate($result);

foreach ($statements as $sql) {
    echo "{$sql};\n";
}

// Output:
// INSERT INTO `posts` (`id`, `title`) VALUES ('10', 'New Post');
// UPDATE `posts` SET `title` = 'Updated' WHERE `id` = '5';
// DELETE FROM `comments` WHERE `id` = '3';
```

---

## SqlGenerator

`Merql\Apply\SqlGenerator` はパラメーター化された SQL ステートメントを生成します。`Applier` と `DryRun` の両方で内部的に使用されます。

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array`（静的）

`list<array{sql: string, params: list<scalar|null>}>` を返します。

```php
use Merql\Apply\SqlGenerator;

$statements = SqlGenerator::generate($result);

foreach ($statements as $stmt) {
    $connection->execute($stmt['sql'], $stmt['params']);
}
```

---

## CellMergeConfig

`Merql\CellMerge\CellMergeConfig` は特定の列または列型に対するセル単位のマージ戦略を設定します。

### `auto(): self`（静的）

TEXT/LONGTEXT列には `TextCellMerger` を、JSON 列には `JsonCellMerger` を割り当てる便利なファクトリーです。

```php
use Merql\CellMerge\CellMergeConfig;

$config = CellMergeConfig::auto();
```

### `forColumn(string $column, CellMerger $merger): self`

特定の列にマージャーを割り当てます。修飾名には `table.column`、すべてのテーブルには `column` を使用してください。

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\TextCellMerger;

$config = (new CellMergeConfig())
    ->forColumn('posts.content', new TextCellMerger())
    ->forColumn('description', new TextCellMerger());
```

### `forType(string $typePattern, CellMerger $merger): self`

型パターンに一致するすべての列にマージャーを割り当てます（大文字と小文字を区別しない部分文字列一致）。

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;

$config = (new CellMergeConfig())
    ->forType('json', new JsonCellMerger());
```

### `getMerger(string $table, string $column, string $columnType = ''): CellMerger`

指定したテーブル、列、型に対応するマージャーを検索します。優先順位：修飾列名、非修飾列名、型パターン、デフォルト（不透明）。

---

## CellMerger（インターフェース）

`Merql\CellMerge\CellMerger` はセル単位のマージ戦略のインターフェースです。

```php
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

interface CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
```

組み込みの実装が3つ用意されています。

### TextCellMerger

`Merql\CellMerge\TextCellMerger` は3方向の行単位diff（gitがファイル内容に使うものと同じアルゴリズム）を使用して、複数行のテキストをマージします。両側が異なる行を編集した場合、競合なくマージされます。同じ行を異なる内容に編集した場合は、競合として報告されます。

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
$result = $merger->merge(
    "line 1\nline 2\nline 3",
    "line 1\nline 2 modified\nline 3",
    "line 1\nline 2\nline 3 modified",
);
// $result->clean === true
// $result->value === "line 1\nline 2 modified\nline 3 modified"
```

### JsonCellMerger

`Merql\CellMerge\JsonCellMerger` は JSON の値をトップレベルのキーごとに比較してマージします。各キーは3方向のロジックでマージされます。ネストされたオブジェクトは不透明な JSON 文字列として比較され、再帰的にはマージされません。

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
$result = $merger->merge(
    '{"a": 1, "b": 2}',
    '{"a": 10, "b": 2}',        // changed "a"
    '{"a": 1, "b": 20}',         // changed "b"
);
// $result->clean === true
// $result->value === '{"a":10,"b":20}'
```

### OpaqueCellMerger

`Merql\CellMerge\OpaqueCellMerger` はデフォルトです。値を不透明な文字列として扱い、値が異なる場合は常に競合として報告します。

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` はすべてのセルマージャーの戻り値の型です。

```php
$result->clean;                    // bool: true if merge succeeded
$result->value;                    // mixed: merged value (or ours on conflict)
$result->conflicts;                // int: number of conflicts

// Factory methods.
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // conflict with count
```

---

## TableSchema

`Merql\Schema\TableSchema` はテーブル構造を読み取り専用で表現したものです。

```php
use Merql\Schema\TableSchema;

$schema = new TableSchema(
    name: 'posts',
    columns: ['id' => 'int', 'title' => 'varchar(255)', 'content' => 'text'],
    primaryKey: ['id'],
    uniqueKeys: [['slug']],
);

$schema->name;                     // string
$schema->columns;                  // array<string, string>: name to type
$schema->primaryKey;               // list<string>
$schema->uniqueKeys;               // list<list<string>>
$schema->hasPrimaryKey();          // bool
$schema->hasUniqueKeys();          // bool
$schema->columnNames();            // list<string>
```

---

## ドライバーシステム

merqlはプラグイン可能なドライバーシステムを使い、データベース固有の操作を抽象化します。ドライバーは接続ドライバーの識別情報から自動検出されます。

### Driver（インターフェース）

`Merql\Driver\Driver` はすべてのデータベースドライバーが実装する契約を定義します。

```php
interface Driver
{
    public function quoteIdentifier(string $name): string;
    public function listTables(DatabaseConnection $connection): array;
    public function readSchema(DatabaseConnection $connection, string $table): TableSchema;
    public function readForeignKeys(DatabaseConnection $connection): array;
    public function selectAll(string $table): string;
}
```

組み込みの実装：`MysqlDriver` と `SqliteDriver`。

### DriverFactory

`Merql\Driver\DriverFactory` は接続からドライバーを自動検出します。

```php
use Merql\Driver\DriverFactory;

$driver = DriverFactory::create($connection);
```

その他のデータベース用にカスタムドライバーを登録します：

```php
DriverFactory::register('pgsql', MyPostgresDriver::class);
```

---

## フィルター

### TableFilter

`Merql\Filter\TableFilter` はglobパターンを使ってテーブルを含めるか除外します。

```php
use Merql\Filter\TableFilter;

$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
```

### ColumnFilter

`Merql\Filter\ColumnFilter` はスナップショットから特定の列を除外します。

```php
use Merql\Filter\ColumnFilter;

$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
```

### RowFilter

`Merql\Filter\RowFilter` は述語関数で行をフィルタリングします。

```php
use Merql\Filter\RowFilter;

$filter = RowFilter::create(function (string $table, array $row): bool {
    // Exclude trashed posts.
    if ($table === 'posts' && ($row['status'] ?? '') === 'trash') {
        return false;
    }
    return true;
});
```

---

## 例外

### SnapshotException

`Merql\Exceptions\SnapshotException` はスナップショットが見つからない場合、または名前が無効な場合にスローされます。

```php
SnapshotException::notFound('baseline', '/path/to/baseline.json');
```

### MergeException

`Merql\Exceptions\MergeException` はマージに必要なスナップショットがない場合にスローされます。

```php
MergeException::snapshotRequired('base');
```

### ConflictException

`Merql\Exceptions\ConflictException` は未解決の競合があるマージ結果を適用しようとした場合にスローされます。

```php
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
```

### SchemaException

`Merql\Exceptions\SchemaException` はスナップショット間でスキーマが異なる場合にスローされるか、収集されます。

```php
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
```

スキーマの不一致があってもマージは停止しません。不一致は収集され、`$result->schemaMismatches()` から確認できます。
