
# خطط الدمج

`MergeResult` هي مخرجات الدمج القابلة للتنفيذ. أما `MergePlan` فهي الصيغة القابلة للفحص لتلك المخرجات. استخدمها عندما يحتاج التطبيق إلى عرض تغييرات قاعدة البيانات أو تجهيزها أو تسلسلها أو تطبيقها بشكل انتقائي قبل كتابتها إلى قاعدة البيانات المستهدفة.

```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()` اللقطات الثلاث المسماة وتدمجها على مستوى الأعمدة، مثل `Merql::merge()`. لإنشاء خطة دمج تستخدم الدمج على مستوى الخلية لأعمدة TEXT وJSON، أنشئ النتيجة بنفسك ومرّرها إلى `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);
```

تتضمن الخطة:

- أسماء اللقطات المستخدمة لإنشائها،
- ملخصًا للعمليات والتعارضات واختلافات المخطط والجداول والمجموعات،
- معرّفات عمليات ثابتة،
- معرّفات مجموعات تغييرات ثابتة،
- أعمدة هوية الصف وقيم الهوية،
- قيم صف الأساس وours وtheirs والنتيجة لكل عملية،
- بيانات وصفية اختيارية للتطبيق المضيف،
- تجزئة الخطة،
- إصدار المخطط.

## المعرّفات الثابتة

تكون معرّفات العمليات حتمية عند تطابق محتوى العملية. وهي مشتقة من اسم الجدول ومفتاح الصف ونوع العملية ومصدرها والأعمدة المتغيرة وقيم النتيجة.

تكون معرّفات مجموعات التغييرات حتمية عند تطابق محتوى المجموعة. ينشئ المُنشئ الافتراضي مجموعة عمليات لكل عملية. ويمكن للتطبيقات المضيفة إنشاء مجموعات أكبر خاصة بالمجال من قائمة العمليات، مع تسلسلها بالشكل نفسه لـ DTO.

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

## بنية العملية

تحتفظ كل `MergePlanOperation` بسياق كافٍ للمراجعة والتراجع.

```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
```

## التحديدات

استخدم `ChangeGroupSelection` عندما يجهّز UI المجموعات. حوّله إلى `OperationSelection` عند التطبيق أو إنشاء عناصر التراجع.

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

النتيجة المحددة هي `MergeResult` نظيفة لا تحتوي إلا على العمليات المحددة. ويمكن تمريرها إلى `Merql::apply()` أو `Merql::applyGuarded()`.

## تحديد ما ينقص الهدف

تحتفظ الخطة بعمليات كلا الطرفين، لأن نتيجة الدمج تحوّل الأساس إلى الحالة المدمجة. عندما يحتوي الهدف على أحد الطرفين بالفعل، حدّد العمليات التي لم تصله بعد فقط. إذا كان الهدف يحتوي على theirs، مثل قاعدة بيانات حية تستقبل تعديلات نسخة معزولة، فهذه كل عملية مصدرها `ours`، إضافة إلى تحديثات `merged` التي تجمع تغييرات الأعمدة من الطرفين:

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

العمليات مصدرها `theirs`، وعمليات الحذف `merged` (الصفوف التي حذفها الطرفان)، موجودة بالفعل في ذلك الهدف.

تظل الخطة المبنية من دمج يتضمن تعارضات تعرض عملية لكل صف متعارض حدّثه الطرفان. تحتوي قيم النتائج على قيمة ours في العمود المتعارض، وتعيد `SelectedMergeResultFactory` نتيجة نظيفة لأي اختيار. وتظل خاصية `conflictState` للعملية ومجموعة التغييرات تعرض `clean` حتى حينها ([#9](https://github.com/inline0/merql/issues/9))، لذا تحقّق من `$plan->conflicts` بنفسك كما سبق، أو حل التعارضات باستخدام `ConflictResolver` وأنشئ الخطة من النتيجة المحلولة باستخدام `MergePlanBuilder`. راجع [التطبيق المحروس](https://merql.inline0.dev/ar/docs/apply/guarded) لكتابة الاختيار مع شروط مسبقة على الصفوف الحية.

## التسلسل

تستخدم خطط الدمج والاختيارات تسلسل JSON ثابتًا. خزّن الخطة بعد تسلسلها عندما تتطلب خطوة تطبيق لاحقة استخدام مجموعة العمليات نفسها التي تمت مراجعتها.

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

## اللقطات ذات الأسماء البديلة

تستخدم خطط الدمج أسماء الجداول الأساسية. إذا اختلفت أسماء الجداول الفعلية بين البيئات، فالتقطها بالاسم الأساسي نفسه قبل التخطيط.

```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',
]));
```

يستخدم المخطط المخزّن في اللقطة اسم الجدول الأساسي، مع الحفاظ على أعمدة الجدول الفعلي ومفاتيحه وأعمدة هويته ومفاتيح صفوفه ومرشحاته.
