Saltar al contenido

Aplicación protegida

GuardedApplier aplica un MergeResult sin conflictos solo si la base de datos de destino aún coincide con la instantánea en vivo esperada. Esto resulta útil cuando se revisó un plan de combinación con anterioridad y es posible que las filas en vivo hayan cambiado antes de aplicarlo.

La configuración habitual promueve los cambios de un entorno aislado a una base de datos en vivo. La base es la base de datos en vivo en el momento en que se copió el entorno aislado; nuestro lado es el entorno aislado editado, y el lado ajeno es la base de datos en vivo actual. El destino es la base de datos en vivo, por lo que la instantánea en vivo esperada es theirs y las operaciones que se escribirán son las que aún no tiene la base de datos en vivo (consulte Selección de operaciones).

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() escribe en la base de datos que se pasa a Merql::init(). La instantánea en vivo esperada debe describir esa misma base de datos: obténgala del destino y asegúrese de que sea lo bastante reciente para que las precondiciones tengan sentido.

Precondiciones

La SQL protegida añade precondiciones sobre las filas en vivo a las instrucciones generadas:

  • las actualizaciones requieren que exista la identidad de la fila de destino y que los valores de las columnas que no son de identidad coincidan con los de la fila en vivo esperada,
  • las eliminaciones requieren que exista la identidad de la fila de destino y que los valores de las columnas que no son de identidad coincidan con los de la fila en vivo esperada,
  • las inserciones requieren que la identidad de destino aún no exista.

Si alguna instrucción no afecta a ninguna fila, merql considera que el plan está obsoleto. La transacción se revierte y el ApplyResult devuelto contiene el error Optimistic precondition failed for guarded merge statement.

En MySQL, una actualización cuyos valores ya coinciden con la fila en vivo también afecta a cero filas, porque MySQL cuenta las filas modificadas, no las coincidentes. Por eso, se considera obsoleta aunque la fila coincida (#2). SQLite cuenta las filas coincidentes y la acepta. Esto puede ocurrir con una actualización merged cuando ambos lados realizaron el mismo cambio en una fila.

Selección de operaciones

Un resultado de combinación contiene las operaciones de ambos lados, ya que transforma la base en el estado combinado. Si se aplica a un destino que ya contiene el estado del lado ajeno, las operaciones de ese lado describen cambios que el destino ya tiene y sus precondiciones fallan:

Operación Estado en un destino que contiene el estado del lado ajeno Seleccionar
ours inserción, actualización o eliminación No está Sí
merged actualización (ambos lados cambiaron la fila) Tiene las columnas del lado ajeno, pero no las del nuestro Sí
theirs inserción La fila existe, por lo que la inserción protegida no afecta a ninguna fila No
merged eliminación (ambos lados eliminaron la fila) La fila ya no está, por lo que la eliminación protegida no afecta a ninguna fila No
theirs actualización o eliminación Ya aplicada No

Por tanto, pasar todo el resultado de Merql::merge() a applyGuarded() falla en cuanto el lado ajeno insertó una fila o ambos lados eliminaron una fila. Primero seleccione las operaciones, por origen como se indica arriba o con un ChangeGroupSelection creado a partir de una UI de revisión. Omita las filas enumeradas en $plan->conflicts: un resultado seleccionado siempre está libre de conflictos y la operación de una fila conflictiva lleva el valor de nuestro lado en la columna en conflicto (consulte Planes de combinación).

Aplicación seleccionada

La aplicación protegida funciona con resultados de combinación seleccionados generados a partir de un plan de combinación, incluidas las selecciones realizadas por grupo de cambios.

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));

Esto permite que una aplicación anfitriona prepare un subconjunto de grupos revisados y siga comprobando que las filas en vivo no hayan cambiado desde que se creó el plan.

Vista previa de SQL

Use SqlGenerator::generateGuarded() para inspeccionar las instrucciones protegidas sin ejecutarlas.

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

Las instrucciones generadas siguen estando parametrizadas. Las precondiciones se expresan mediante cláusulas WHERE adicionales para las actualizaciones y eliminaciones, y comprobaciones NOT EXISTS para las inserciones.

Aplicación protegida o normal

Merql::apply() ejecuta las mismas operaciones sin precondiciones. En ese caso, las actualizaciones y eliminaciones se realizan tanto si la fila en vivo ha cambiado desde que se creó el plan como si no, y la inserción de una fila existente falla con el error de clave duplicada de la base de datos. Use la aplicación normal para un destino que sabe que aún contiene la base y la aplicación protegida cuando otros escritores puedan haber modificado el destino desde que se creó su instantánea.