
# CLI 참조

merql에는 터미널에서 직접 스냅샷을 만들고 차이를 계산하며 병합을 실행하는 CLI이 포함되어 있습니다. Composer은 이를 `vendor/bin/merql`로 설치하며, merql 저장소를 체크아웃하면 `bin/merql`입니다.

merql이 0.4.1을 제공하므로 `vendor/bin/merql`은 종속성으로 설치해도 작동합니다. 이전 릴리스는 Composer 자동 로더를 패키지 자체의 `vendor/` 디렉터리에서만 찾았기 때문에 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에서 분기된 두 데이터베이스에서 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
```

세 스냅샷을 모두 같은 데이터베이스에서 차례로 만들면 병합에 사용할 수 없습니다. 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) 참고). 이름에는 영숫자, 하이픈, 밑줄만 사용할 수 있습니다.

일반적인 워크플로는 변경 전 스냅샷을 캡처한 뒤 변경 후에 하나 더 캡처하는 것입니다. 두 스냅샷을 바탕으로 차이를 계산하거나 병합합니다.

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

이름이 지정된 두 스냅샷을 비교하고 차이 요약을 표시합니다.

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

출력:

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

두 스냅샷이 모두 이미 있어야 합니다(`merql snapshot`으로 이전에 캡처). 저장된 스냅샷 파일을 대상으로 작업하므로 diff에는 데이터베이스 연결이 필요하지 않습니다.

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

이름이 지정된 세 스냅샷을 3방향으로 병합합니다.

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

출력:

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

세 스냅샷이 모두 이미 있어야 합니다. 저장된 스냅샷 파일을 대상으로 작업하고 데이터베이스에 기록하지 않으므로 병합에는 데이터베이스 연결이 필요하지 않습니다. CLI에는 apply 명령이 없습니다. 병합 결과를 적용하려면 PHP API를 사용합니다.

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`를 출력하고 성공 상태로 종료합니다.

시나리오 구성은 [테스트 전략](https://merql.inline0.dev/ko/docs/advanced/testing)을 참고하세요.

## 종료 상태

`merql snapshot`, `diff`, `merge`는 인수가 누락되면 상태 1로 종료하고, `snapshot`는 연결이 설정되지 않은 경우에도 상태 1로 종료합니다. 스냅샷이 없으면 처리되지 않은 `SnapshotException`로 명령이 끝나고 0이 아닌 상태를 반환합니다. 알 수 없는 명령은 `Unknown command`를 출력하지만 현재 상태 0으로 종료합니다([#3](https://github.com/inline0/merql/issues/3)).

## 스냅샷 저장

스냅샷은 작업 디렉터리 기준 `.merql/snapshots/`에 저장됩니다. 각 스냅샷은 `<name>.json`라는 이름의 단일 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()`로 불러올 수 있으며, 반대 방향도 가능합니다.
