본문으로 건너뛰기

가드 적용

GuardedApplier은 대상 데이터베이스가 예상 라이브 스냅샷과 일치할 때만 충돌 없는 MergeResult을 적용합니다. 병합 계획을 미리 검토했고 적용 전에 라이브 행이 변경되었을 수 있는 경우 유용합니다.

일반적인 설정에서는 샌드박스의 편집 내용을 라이브 데이터베이스로 승격합니다. 기준은 샌드박스를 복사한 시점의 라이브 상태이고, ours는 편집한 샌드박스이며, theirs는 현재 라이브 상태입니다. 대상은 라이브 데이터베이스이므로 예상 라이브 스냅샷은 theirs이고, 쓸 작업은 라이브에 아직 없는 작업입니다(작업 선택 참조).

PHP
use Merql\Connection;
use Merql\Merql;
use Merql\Plan\OperationSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

Merql::init(Connection::sqlite('/data/live.sqlite'));

$plan = Merql::plan('promote-1', 'base', 'ours', 'theirs');

// Skip rows with unresolved conflicts.
$conflicted = [];
foreach ($plan->conflicts as $conflict) {
    $conflicted[$conflict->table . "\0" . $conflict->rowKey] = true;
}

$ids = [];
foreach ($plan->operations as $operation) {
    $missing = $operation->source === 'ours'
        || ($operation->source === 'merged' && $operation->type === 'update');

    if ($missing && !isset($conflicted[$operation->table . "\0" . $operation->rowKey])) {
        $ids[] = $operation->id;
    }
}

$selected = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    OperationSelection::fromIds($ids),
    SnapshotStore::load('base'),
);

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

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo $error . "\n";
    }
}

Merql::applyGuarded()은 Merql::init()에 전달된 데이터베이스에 씁니다. 예상 라이브 스냅샷은 같은 데이터베이스를 설명해야 합니다. 대상에서 가져오고, 사전 조건이 의미를 가질 만큼 최근에 캡처하세요.

사전 조건

가드 적용 SQL은 생성된 문에 라이브 행 사전 조건을 추가합니다:

  • 업데이트는 대상 행 식별자가 존재하고 비식별 열 값이 예상 라이브 행과 일치해야 합니다.
  • 삭제는 대상 행 식별자가 존재하고 비식별 열 값이 예상 라이브 행과 일치해야 합니다.
  • 삽입은 대상 식별자가 아직 존재하지 않아야 합니다.

문 실행이 행에 영향을 주지 않으면 merql은 계획이 오래된 것으로 처리합니다. 트랜잭션이 롤백되고 반환된 ApplyResult에는 오류 Optimistic precondition failed for guarded merge statement.이 포함됩니다.

MySQL에서는 값이 이미 라이브 행과 같은 업데이트도 변경된 행이 아닌 일치한 행을 MySQL이 계산하므로 행에 영향을 주지 않습니다. 따라서 행이 일치하더라도 해당 업데이트는 오래된 것으로 실패합니다(#2). SQLite는 일치한 행을 계산하므로 이를 허용합니다. 양쪽에서 같은 행을 똑같이 변경한 경우, merged 업데이트에서 이런 일이 발생할 수 있습니다.

작업 선택

병합 결과에는 양쪽의 작업이 모두 포함됩니다. 기준 상태를 병합된 상태로 바꾸기 때문입니다. 이미 theirs 상태인 대상에 적용하면 theirs에서 비롯된 작업은 대상에 이미 반영된 변경을 설명하므로 사전 조건이 실패합니다:

작업 theirs 상태를 보유한 대상의 상태 선택
ours 삽입, 업데이트 또는 삭제 없음 예
merged 업데이트(양쪽에서 행을 변경함) theirs의 열은 있고 ours의 열은 없음 예
theirs 삽입 행이 존재하므로 가드 삽입은 영향을 주는 행이 0개입니다 아니요
merged 삭제(양쪽에서 행을 삭제함) 행이 없으므로 가드 삭제는 영향을 주는 행이 0개입니다 아니요
theirs 업데이트 또는 삭제 이미 적용됨 아니요

따라서 Merql::merge()의 전체 결과를 applyGuarded()에 전달하면 theirs에서 행을 삽입했거나 양쪽에서 행을 삭제한 경우 즉시 실패합니다. 위와 같이 출처별로 작업을 선택하거나 검토 UI에서 만든 ChangeGroupSelection를 사용해 작업을 먼저 선택하세요. $plan->conflicts에 나열된 행은 제외하세요. 선택한 결과는 항상 충돌이 없으며, 충돌한 행의 작업은 충돌 열에 ours의 값을 포함합니다(병합 계획 참조).

선택 적용

가드 적용은 변경 그룹별로 선택한 항목을 포함해 병합 계획에서 생성한 선택된 병합 결과와 함께 작동합니다.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Snapshot\SnapshotStore;

$selection = ChangeGroupSelection::fromIds([$plan->changeGroups[0]->id]);
$selected = (new SelectedMergeResultFactory())->fromChangeGroupSelection(
    $plan,
    $selection,
    SnapshotStore::load($plan->baseSnapshot),
);

$applied = Merql::applyGuarded($selected, SnapshotStore::load($plan->theirsSnapshot));

이를 통해 호스트 애플리케이션은 검토한 그룹의 일부를 준비하면서 계획 생성 후 라이브 행이 바뀌지 않았음을 확인할 수 있습니다.

SQL 미리보기

SqlGenerator::generateGuarded()을 사용해 가드 문을 실행하지 않고 검사하세요.

PHP
use Merql\Apply\SqlGenerator;
use Merql\Snapshot\SnapshotStore;

$statements = SqlGenerator::generateGuarded(
    $selected,
    SnapshotStore::load($plan->theirsSnapshot),
);

foreach ($statements as $statement) {
    echo $statement['sql'] . "\n";
}

생성된 문은 계속 매개변수화되어 있습니다. 사전 조건은 업데이트/삭제의 추가 WHERE 절과 삽입의 NOT EXISTS 검사로 표현됩니다.

가드 적용 또는 일반 적용

Merql::apply()은 사전 조건 없이 같은 작업을 실행합니다. 그러면 계획을 만든 뒤 라이브 행이 변경되었는지와 관계없이 업데이트와 삭제가 성공하며, 기존 행을 삽입하면 데이터베이스의 중복 키 오류가 발생합니다. 대상이 여전히 기준 상태라고 확신하는 경우에는 일반 적용을 사용하고, 스냅샷을 만든 뒤 다른 작성자가 대상을 수정했을 가능성이 있는 경우에는 가드 적용을 사용하세요.