Διαχείριση συγκρούσεων
Σύγκρουση προκύπτει όταν οι δύο πλευρές μιας συγχώνευσης αλλάζουν τα ίδια δεδομένα με ασύμβατους τρόπους. Το merql εντοπίζει τέσσερις τύπους συγκρούσεων και παρέχει στρατηγικές αυτόματης και χειροκίνητης επίλυσης.
$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
Κάθε σύγκρουση είναι ένα αντικείμενο μόνο για ανάγνωση με τους εξής προσπελάσιμους δείκτες:
$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).
Αποκωδικοποίηση του πρωτεύοντος κλειδιού
Το κλειδί γραμμής είναι κωδικοποιημένη συμβολοσειρά. Για να λάβετε πίνακα πρωτευόντων κλειδιών με ονομασμένα πεδία, μεταβιβάστε τις στήλες ταυτότητας:
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']
$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
Αυτόματη επίλυση με το ConflictPolicy
Το ConflictResolver εφαρμόζει μια πολιτική για την επίλυση όλων των συγκρούσεων ταυτόχρονα:
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 έχει κενή λίστα διενέξεων και μπορεί να εφαρμοστεί απευθείας.
Μη αυτόματη επίλυση
Για λεπτομερή έλεγχο, εξετάστε κάθε διένεξη και δημιουργήστε τη δική σας επίλυση:
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:
use Merql\Exceptions\ConflictException;
try {
$applier->apply($result);
} catch (ConflictException $e) {
echo $e->getMessage();
// "Cannot apply merge with 3 unresolved conflicts"
}
Να επιλύετε πάντα τις διενέξεις πριν από την εφαρμογή, είτε με ConflictResolver::resolve() είτε δημιουργώντας χειροκίνητα ένα καθαρό MergeResult.