Pāriet uz saturu

Trīsvirzienu sapludināšana

Trīsvirzienu sapludināšanā salīdzina divas izmaiņu kopas ar kopīgu priekšteci un apvieno tās vienā rezultātā. merql piemēro šo algoritmu datubāzes rindām, nevis failu rindām.

PHP
use Merql\Merge\ThreeWayMerge;

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

echo $result->operationCount(); // resolved operations
echo $result->conflictCount();  // unresolved conflicts

Trīs stāvokļi

Katrai sapludināšanai nepieciešami tieši trīs momentuzņēmumi:

  • Bāze -- datubāzes stāvoklis brīdī, kad abas atzari sadalījās.
  • Mūsu -- viens atzars pēc tajā veiktajām izmaiņām, piemēram, rediģēta smilškastes kopija.
  • Viņu -- otrs atzars pēc tajā veiktajām izmaiņām, piemēram, nepārtraukti darbojusies reāllaika datubāze.

Mūsu un viņu versija jāiegūst no datubāzes, kurā atrodas attiecīgais atzars. Sapludināšanas noteikumi abas puses aplūko vienādi; nosaukumi nosaka katras darbības source, konfliktu veidu nosaukumus (update_delete nozīmē, ka mēs atjauninājām, bet viņi izdzēsa) un to, kurai pusei par labu nosveras ConflictPolicy::OursWins un TheirsWins.

Kods
         Base (snapshot at fork time)
        /                              \
   Ours (our changes)                   Theirs (their changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)

Bez kopīgas bāzes nav iespējams atšķirt “pievienots” no “nemainīts”. Divvirzienu salīdzinājumā redzams, ka A pusei ir vērtība, bet B pusei tās nav, taču nav iespējams noteikt, vai A to pievienoja vai B izdzēsa. Uz šo jautājumu atbild bāze.

Kā tiek aprēķinātas izmaiņu kopas

Differ salīdzina katru momentuzņēmumu ar bāzi, lai izveidotu divas izmaiņu kopas. Katrā izmaiņu kopā ir trīs veidu darbības:

Bāze Pašreizējais stāvoklis Darbība
Rinda pastāv Rinda pastāv, pirkstu nospiedums nemainīgs Bez izmaiņām
Rinda pastāv Rinda pastāv, pirkstu nospiedums atšķiras UPDATE (kolonnu atšķirības)
Rinda pastāv Rindas nav DELETE
Rindas nav Rinda pastāv INSERT

Pirkstu nospiedumi (SHA-256 — normalizētas rindas satura jaucējvērtības) nodrošina ātru pārbaudi. Ja divas jaucējvērtības sakrīt, rinda nav mainījusies un kolonnu līmeņa salīdzināšana nav vajadzīga.

PHP
use Merql\Diff\Differ;

$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);

Sapludināšanas noteikumi

Pēc tam abas izmaiņu kopas tiek sapludinātas pa vienai darbībai. Šī tabula nosaka iznākumu visām iespējamām kombinācijām:

Mūsu Viņu Rezultāts
Bez izmaiņām Bez izmaiņām Paturēt sākotnējo
Bez izmaiņām Atjaunināts Pieņemt viņu izmaiņas
Atjaunināts Bez izmaiņām Pieņemt mūsu izmaiņas
Atjaunināts (tā pati vērtība) Atjaunināts (tā pati vērtība) Pieņemt (abi piekrīt)
Atjaunināts (atšķirīga vērtība) Atjaunināts (atšķirīga vērtība) Konflikts
Bez izmaiņām Dzēsts Pieņemt dzēšanu
Dzēsts Bez izmaiņām Pieņemt dzēšanu
Dzēsts Dzēsts Pieņemt dzēšanu (abi piekrīt)
Atjaunināts Dzēsts Konflikts
Dzēsts Atjaunināts Konflikts
Ievietots (tikai mūsu pusē) -- Pieņemt ievietošanu
-- Ievietots (tikai viņu pusē) Pieņemt ievietošanu
Ievietots (tā pati PK) Ievietots (tā pati PK) Konflikts

Ja abas puses atjaunina vienu un to pašu rindu, merql uzreiz nekonstatē konfliktu. Tas pāriet uz kolonnu līmeņa apvienošanu, neatkarīgi salīdzinot katru kolonnu. Sīkāku informāciju skatiet sadaļā Apvienošana kolonnu līmenī.

Fasādes izmantošana

Merql fasāde darbojas ar nosauktiem momentuzņēmumiem, kas saglabāti diskā. Pirms momentuzņēmumu izveides izsauciet Merql::init(), norādot datubāzi, kurā ir katrs stāvoklis:

PHP
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');

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

Neveidojiet visus trīs momentuzņēmumus secīgi no vienas datubāzes. Tad viņu momentuzņēmumā būtu arī mūsu izmaiņas, tāpēc katra mūsu ievietotā rinda abās pusēs izskatītos kā ievietota un tiktu uzrādīta kā insert_insert konflikts.

Merql::merge(), Merql::patch() un Merql::plan() izveido ThreeWayMerge bez CellMergeConfig, tāpēc apvienošana notiek tikai kolonnu līmenī. Lai apvienotu šūnu līmenī, ielādējiet momentuzņēmumus un izmantojiet tieši šo klasi, kā parādīts tālāk.

Tieša klašu izmantošana

Lai iegūtu lielāku kontroli, izveidojiet komponentu instances paši. Snapshotter::capture() neko nesaglabā, tāpēc paturiet momentuzņēmumus atmiņā vai saglabājiet tos ar SnapshotStore::save():

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;

$base   = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours   = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
    SnapshotStore::load('base'),
    SnapshotStore::load('ours'),
    SnapshotStore::load('theirs'),
);

Īsceļš ar labojumu kopu

Ja izmaiņas ir tikai vienā pusē (nav vienlaicīgu izmaiņu), izmantojiet patch(). Tā ir divvirzienu apvienošana, kurā mūsu puse sakrīt ar sākotnējo stāvokli, tāpēc konflikti nav iespējami:

PHP
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.

Ar fasādi:

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

patch() ir līdzvērtīgs merge($base, $base, $changes). Tā kā mūsu puse nekad nav atšķīrusies no sākotnējā stāvokļa, visas viņu izmaiņas tiek pieņemtas bez konfliktiem.

Shēmas neatbilstību noteikšana

Ja starp momentuzņēmumiem tabulai pievienotas vai no tās noņemtas kolonnas, apvienošana joprojām notiek, taču neatbilstības tiek reģistrētas. Mainīti kolonnu tipi netiek salīdzināti, bet pārdēvēta kolonna tiek uzrādīta kā viena noņemta un viena pievienota kolonna; skatiet Shēmas validācija.

PHP
$result = $merge->merge($base, $ours, $theirs);

if ($result->hasSchemaMismatches()) {
    foreach ($result->schemaMismatches() as $mismatch) {
        echo $mismatch->getMessage() . "\n";
    }
}

merql shēmas nemigrē automātiski. Par shēmas izmaiņām atbild izsaucējs. Apvienošanas rezultātā tās tiek uzrādītas, lai jūs varētu izlemt, kā rīkoties tālāk.

MergeResult

Rezultāta objektā ir viss nepieciešamais:

PHP
$result->isClean();            // true if no conflicts
$result->operations();         // list of MergeOperation objects
$result->conflicts();          // list of Conflict objects
$result->operationCount();     // number of resolved operations
$result->conflictCount();      // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot();       // the base snapshot used for the merge

Katram MergeOperation ir tips (insert, update vai delete), tabulas nosaukums, rindas atslēga, kolonnu vērtības un avots (ours, theirs vai merged). merged apzīmē rindu, ko abas puses atjauninājušas (apvienojot izmaiņas katrā kolonnā atsevišķi), vai rindu, ko abas puses dzēsušas.

Kur var izmantot apvienošanas rezultātu

Apvienošanas rezultātā aprakstīts, kā no sākotnējā stāvokļa iegūt apvienoto stāvokli. Tās darbības ietver abu pušu izmaiņas: gan mūsu, gan viņu ievietotā rinda tiek iekļauta kā ievietojama.

  • Merql::apply() (vai Applier) izpilda katru darbību pievienotajā datubāzē. To var bez problēmām lietot datubāzē, kurā joprojām ir sākotnējais stāvoklis, piemēram, jaunā sākotnējā stāvokļa kopijā. Ja datubāzē jau ir vienas puses izmaiņas, esošas rindas ievietošana neizdosies un transakcija tiks atcelta.
  • Lai iekļautu mūsu izmaiņas datubāzē, kurā jau ir viņu izmaiņas, piemēram, pārnestu smilškastes labojumus uz darbības vidi, lietojiet tikai tās darbības, kuru datubāzē trūkst: darbības ar avotu ours un merged atjaunināšanas. Izmantojiet apvienošanas plānu, lai tās atlasītu, un aizsargāto lietošanu, lai tās ierakstītu, pārbaudot darbības vides rindu priekšnosacījumus.