Vai al contenuto

Piani di unione

Un MergeResult è il risultato eseguibile di un’unione. Un MergePlan è la forma ispezionabile di quel risultato. Usatelo quando un’applicazione deve mostrare, preparare, serializzare o applicare selettivamente le modifiche al database prima di scriverle nel database di destinazione.

PHP
use Merql\Merql;

$plan = Merql::plan(
    id: 'plan-2026-06-06',
    base: 'base',
    ours: 'ours',
    theirs: 'theirs',
    metadata: ['sandbox_id' => 'ow_123'],
);

Merql::plan() carica i tre snapshot nominati e li unisce a livello di colonna, come Merql::merge(). Per pianificare un’unione che usa l’unione a livello di cella per le colonne TEXT e JSON, create voi il risultato e passatelo a MergePlanBuilder:

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;

$base = SnapshotStore::load('base');
$ours = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);
$plan = (new MergePlanBuilder())->build('plan-2026-06-06', $base, $ours, $theirs, $result);

Il piano contiene:

  • i nomi degli snapshot usati per crearlo,
  • un riepilogo di operazioni, conflitti, incompatibilità dello schema, tabelle e gruppi,
  • ID operazione stabili,
  • ID gruppo di modifiche stabili,
  • colonne di identità delle righe e valori di identità,
  • valori delle righe di base, nostri, altrui e del risultato per ogni operazione,
  • metadati facoltativi dell’applicazione host,
  • un hash del piano,
  • una versione dello schema.

ID stabili

Gli ID operazione sono deterministici a parità di contenuto dell’operazione. Derivano dal nome della tabella, dalla chiave della riga, dal tipo di operazione, dall’origine dell’operazione, dalle colonne modificate e dai valori del risultato.

Gli ID dei gruppi di modifiche sono deterministici a parità di contenuto del gruppo. Il builder predefinito crea un gruppo di operazioni per ogni operazione. Le applicazioni host possono creare gruppi più ampi e specifici del dominio a partire dall’elenco delle operazioni, mantenendo lo stesso formato DTO nella serializzazione.

PHP
foreach ($plan->changeGroups as $group) {
    echo $group->id . ': ' . implode(', ', $group->operationIds);
}

Struttura dell’operazione

Ogni MergePlanOperation conserva il contesto necessario per la revisione e il rollback.

PHP
$operation = $plan->operations[0];

$operation->id;              // stable operation ID
$operation->type;            // insert, update, delete
$operation->table;           // canonical table name
$operation->rowKey;          // encoded identity key
$operation->identityColumns; // columns used for identity
$operation->identityValues;  // decoded identity values
$operation->source;          // ours, theirs, or merged
$operation->baseRow;         // row in base snapshot, or null
$operation->oursRow;         // row in ours snapshot, or null
$operation->theirsRow;       // row in theirs snapshot, or null
$operation->resultValues;    // values the merge would write
$operation->changedColumns;  // changed result columns

Selezioni

Usate ChangeGroupSelection quando UI prepara i gruppi. Convertitelo in un OperationSelection durante l’applicazione o la generazione degli artefatti di rollback.

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

$selection = ChangeGroupSelection::fromIds([
    $plan->changeGroups[0]->id,
]);

$operationSelection = $selection->toOperationSelection($plan);

$selectedResult = (new SelectedMergeResultFactory())->fromOperationSelection(
    $plan,
    $operationSelection,
    SnapshotStore::load($plan->baseSnapshot),
);

Il risultato selezionato è un MergeResult senza conflitti che contiene solo le operazioni selezionate. Può essere passato a Merql::apply() o a Merql::applyGuarded().

Selezionare le operazioni mancanti nel database di destinazione

Un piano contiene le operazioni di entrambi i lati, perché il risultato dell’unione trasforma la base nello stato unito. Quando il database di destinazione contiene già un lato, selezionate solo le operazioni che gli mancano. Se contiene i dati altrui, per esempio un database live che riceve le modifiche di una copia sandbox, si tratta di tutte le operazioni con origine ours, più gli aggiornamenti merged, che combinano le modifiche alle colonne di entrambi i lati:

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

// Rows with an unresolved conflict stay out of the selection.
$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($plan->baseSnapshot),
);

Le operazioni con origine theirs e le eliminazioni merged (righe eliminate da entrambi i lati) sono già presenti in quel database.

Un piano creato da un’unione con conflitti elenca comunque un’operazione per ogni riga in conflitto aggiornata da entrambi i lati. I valori del risultato contengono il nostro valore nella colonna in conflitto, e SelectedMergeResultFactory restituisce un risultato senza conflitti per qualsiasi selezione. Anche in questo caso, lo conflictState dell’operazione e del gruppo di modifiche indica attualmente clean (#9), quindi verificate voi $plan->conflicts come sopra oppure risolvete i conflitti con ConflictResolver e create il piano dal risultato risolto usando MergePlanBuilder. Consultate Applicazione protetta per scrivere la selezione con precondizioni sulle righe live.

Serializzazione

I piani di unione e le selezioni hanno una serializzazione JSON stabile. Salvate il piano serializzato se un passaggio di applicazione successivo deve usare esattamente l’insieme di operazioni revisionato.

PHP
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\MergePlanSerializer;

$json = MergePlanSerializer::toJson($plan);
$restored = MergePlanSerializer::fromJson($json);

$selectionJson = json_encode($selection->toArray(), JSON_THROW_ON_ERROR);
$selection = ChangeGroupSelection::fromArray(
    json_decode($selectionJson, true, flags: JSON_THROW_ON_ERROR),
);

Snapshot con alias

I piani di unione usano nomi canonici per le tabelle. Se i nomi fisici delle tabelle differiscono tra ambienti, acquisitele con lo stesso nome canonico prima di creare il piano.

PHP
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

SnapshotStore::save($snapshotter->captureAliased('live', [
    'wp_posts' => 'wp_posts',
]));

SnapshotStore::save($snapshotter->captureAliased('sandbox', [
    'wp_onumia_abc_posts' => 'wp_posts',
]));

Lo schema salvato nello snapshot usa il nome canonico della tabella, preservando colonne, chiavi, colonne di identità, chiavi delle righe e filtri della tabella fisica.