Μετάβαση στο περιεχόμενο

Διαχείριση συγκρούσεων

Σύγκρουση προκύπτει όταν οι δύο πλευρές μιας συγχώνευσης αλλάζουν τα ίδια δεδομένα με ασύμβατους τρόπους. Το merql εντοπίζει τέσσερις τύπους συγκρούσεων και παρέχει στρατηγικές αυτόματης και χειροκίνητης επίλυσης.

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

if (!$result->isClean()) {
    foreach ($result->conflicts() as $conflict) {
        echo "{$conflict->table()}.{$conflict->column()}: "
           . "{$conflict->oursValue()} vs {$conflict->theirsValue()}\n";
    }
}

Τύποι συγκρούσεων

update_update

Και οι δύο πλευρές άλλαξαν την ίδια στήλη της ίδιας γραμμής σε διαφορετικές τιμές. Αυτός είναι ο συνηθέστερος τύπος σύγκρουσης.

Κώδικας
Base:    posts #42  title = "Hello"
Ours:    posts #42  title = "Welcome"
Theirs:  posts #42  title = "Greetings"

Η σύγκρουση αναφέρει: πίνακας posts, κλειδί γραμμής 42, στήλη title, τιμή ours Welcome, τιμή theirs Greetings, τιμή βάσης Hello.

update_delete

Η μία πλευρά ενημέρωσε μια γραμμή και η άλλη τη διέγραψε. Το merql δεν μπορεί να προσδιορίσει αν πρέπει να επικρατήσει η ενημέρωση ή η διαγραφή.

Κώδικας
Ours:    posts #42  title = "Updated Title"    (updated)
Theirs:  posts #42  (deleted)

Η σύγκρουση αναφέρει: πίνακας posts, κλειδί γραμμής 42, τύπος update_delete. Η τιμή ours περιέχει ολόκληρη την ενημερωμένη γραμμή. Η τιμή theirs είναι null.

delete_update

Το αντίστροφο του update_delete. Το theirs ενημέρωσε τη γραμμή και το ours τη διέγραψε.

Κώδικας
Ours:    posts #42  (deleted)
Theirs:  posts #42  title = "Updated Title"    (updated)

Η σύγκρουση αναφέρει: τύπος delete_update. Η τιμή ours είναι null, ενώ η τιμή theirs περιέχει ολόκληρη την ενημερωμένη γραμμή.

insert_insert

Και οι δύο πλευρές εισήγαγαν γραμμή με το ίδιο πρωτεύον κλειδί. Επειδή και οι δύο γραμμές είναι νέες (δεν υπάρχουν στη βάση), το merql δεν μπορεί να τις συγχωνεύσει.

Κώδικας
Base:    (no row #99)
Ours:    posts #99  { title: "My Post" }
Theirs:  posts #99  { title: "Their Post" }

Η σύγκρουση αναφέρει: τύπος insert_insert. Η τιμή ours περιέχει τα δεδομένα της γραμμής του ours και η τιμή theirs τα δεδομένα της γραμμής του theirs.

Η κλάση Conflict

Κάθε σύγκρουση είναι ένα αντικείμενο μόνο για ανάγνωση με τους εξής προσπελάσιμους δείκτες:

PHP
$conflict->table();      // string: table name
$conflict->rowKey();     // string: encoded row identity key
$conflict->type();       // string: "update_update"|"update_delete"|"delete_update"|"insert_insert"
$conflict->column();     // ?string: column name (null for structural conflicts)
$conflict->oursValue();  // mixed: our value (null for delete side, full row for structural)
$conflict->theirsValue(); // mixed: their value (null for delete side, full row for structural)
$conflict->baseValue();  // mixed: base value (null for inserts)

Η μέθοδος column() επιστρέφει όνομα στήλης για συγκρούσεις update_update και null για δομικές συγκρούσεις (update_delete, delete_update, insert_insert).

Αποκωδικοποίηση του πρωτεύοντος κλειδιού

Το κλειδί γραμμής είναι κωδικοποιημένη συμβολοσειρά. Για να λάβετε πίνακα πρωτευόντων κλειδιών με ονομασμένα πεδία, μεταβιβάστε τις στήλες ταυτότητας:

PHP
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']

$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']

Αυτόματη επίλυση με το ConflictPolicy

Το ConflictResolver εφαρμόζει μια πολιτική για την επίλυση όλων των συγκρούσεων ταυτόχρονα:

PHP
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;

// One side wins everywhere.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Manual: return the result unchanged (inspect conflicts yourself).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);

Απαρίθμηση ConflictPolicy

Τιμή Συμπεριφορά
OursWins Χρήση της τιμής ours για το update_update. Διατήρηση της ενημέρωσης για το update_delete. Διατήρηση της διαγραφής για το delete_update. Χρήση της εισαγωγής ours για το insert_insert.
TheirsWins Χρήση της τιμής theirs για το update_update. Διαγραφή για το update_delete. Διατήρηση της ενημέρωσης για το delete_update. Χρήση της εισαγωγής theirs για το insert_insert.
Manual Επιστροφή του αποτελέσματος χωρίς αλλαγές. Όλες οι συγκρούσεις παραμένουν ανεπίλυτες.

Πώς λειτουργεί η επίλυση

Για διενέξεις update_update, ο επιλυτής τροποποιεί την υπάρχουσα πράξη συγχώνευσης για τη συγκεκριμένη γραμμή, αντικαθιστώντας την τιμή της στήλης που προκάλεσε τη διένεξη με την τιμή της πλευράς που επικράτησε.

Για δομικές διενέξεις (update_delete, delete_update, insert_insert), ο επιλυτής προσθέτει μια νέα πράξη στο αποτέλεσμα:

  • Το update_delete με OursWins προσθέτει μια πράξη UPDATE.
  • Το update_delete με TheirsWins προσθέτει μια πράξη DELETE.
  • Το delete_update με OursWins προσθέτει μια πράξη DELETE.
  • Το delete_update με TheirsWins προσθέτει μια πράξη UPDATE.
  • Το insert_insert χρησιμοποιεί τις τιμές γραμμής της πλευράς που επικράτησε.

Το επιλυμένο MergeResult έχει κενή λίστα διενέξεων και μπορεί να εφαρμοστεί απευθείας.

Μη αυτόματη επίλυση

Για λεπτομερή έλεγχο, εξετάστε κάθε διένεξη και δημιουργήστε τη δική σας επίλυση:

PHP
foreach ($result->conflicts() as $conflict) {
    echo "Table:  {$conflict->table()}\n";
    echo "Row:    {$conflict->rowKey()}\n";
    echo "Type:   {$conflict->type()}\n";

    if ($conflict->column() !== null) {
        echo "Column: {$conflict->column()}\n";
        echo "Base:   " . var_export($conflict->baseValue(), true) . "\n";
        echo "Ours:   " . var_export($conflict->oursValue(), true) . "\n";
        echo "Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
    }
}

Εφαρμογή με ανεπίλυτες διενέξεις

Το Applier αρνείται να εφαρμόσει αποτέλεσμα συγχώνευσης με ανεπίλυτες διενέξεις. Προκαλεί εξαίρεση ConflictException:

PHP
use Merql\Exceptions\ConflictException;

try {
    $applier->apply($result);
} catch (ConflictException $e) {
    echo $e->getMessage();
    // "Cannot apply merge with 3 unresolved conflicts"
}

Να επιλύετε πάντα τις διενέξεις πριν από την εφαρμογή, είτε με ConflictResolver::resolve() είτε δημιουργώντας χειροκίνητα ένα καθαρό MergeResult.