
# البدء

يشرح هذا الدليل التثبيت والاتصال بقاعدة بيانات والتقاط لقطات الحالة وحساب الفروق وإجراء دمج ثلاثي.

## التثبيت

```bash
composer require merql/merql
```

يتطلب merql الإصدار PHP 8.2 أو أحدث مع `ext-mbstring` و`ext-zlib` (وكلاهما مطلوب من تبعية `pitmaster/pitmaster`). وتحتاج أيضًا إلى امتداد المحوّل الذي تستخدمه: `ext-pdo` مع `ext-pdo_sqlite` أو `ext-pdo_mysql` لمحوّل PDO، أو `ext-mysqli` لمحوّل mysqli. يقترح Composer هذه الامتدادات فقط، لذا تحقّق من تثبيت الامتداد الذي تحتاج إليه.

تثبّت الحزمة CLI باسم `vendor/bin/merql`. استخدم merql 0.4.1 أو أحدث من أجل CLI؛ ففي الإصدارات الأقدم، لم يكن الملف التنفيذي يعثر على محمّل الفئات التلقائي عند تثبيت merql كتابعية.

## الاتصال بقاعدة بيانات

يعمل merql عبر واجهة صغيرة لمحوّل الاتصال. ويتضمن محوّلات مدمجة لـ PDO وmysqli، بالإضافة إلى مشغّلات لهجات مدمجة لـ MySQL وSQLite.

```php
use Merql\Connection;

// SQLite (in-memory or file-based, through PDO).
$connection = Connection::sqlite(':memory:');
$connection = Connection::sqlite('/path/to/database.sqlite');

// MySQL through PDO.
$connection = Connection::mysql('127.0.0.1', 'my_database', 'root', 'password');

// MySQL through mysqli.
$connection = Connection::mysqli('127.0.0.1', 'my_database', 'root', 'password');

// Any MySQL or SQLite PDO DSN.
$connection = Connection::fromDsn('mysql:host=127.0.0.1;dbname=my_database;charset=utf8mb4', 'root', 'password');
```

يفتح `fromDsn()` أي DSN يدعمه PDO، لكن merql لا يتضمن مشغّلات لهجات إلا لـ MySQL وSQLite. مع محرك آخر مثل `pgsql:`، يُفتح الاتصال لكن `Merql::init()` يطلق `Unsupported database driver: pgsql` إلى أن تسجّل مشغّلًا له. راجع [مشغّلات قواعد البيانات](https://merql.inline0.dev/ar/docs/apply/drivers).

## التقاط لقطة حالة

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

```php
use Merql\Merql;

Merql::init($connection);

// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');

// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);
```

تُحفظ لقطات الحالة افتراضيًا كملفات JSON في `.merql/snapshots/`. يمكنك تغيير هذا الموقع:

```php
use Merql\Snapshot\SnapshotStore;

SnapshotStore::setDirectory('/var/data/merql-snapshots');
```

## حساب الفروق

تقارن مقارنة الفروق بين لقطتي حالة وتُرجع مجموعة تغييرات من عمليات الإدراج والتحديث والحذف. يجب أن تكون اللقطتان موجودتين مسبقًا؛ هنا `current` لقطة ثانية التُقطت بعد تغيير قاعدة البيانات.

```php
$changeset = Merql::diff('baseline', 'current');

echo "Inserts: " . count($changeset->inserts()) . "\n";
echo "Updates: " . count($changeset->updates()) . "\n";
echo "Deletes: " . count($changeset->deletes()) . "\n";

// Inspect changes for a single table.
$postChanges = $changeset->forTable('posts');

// Check per-column diffs on updates.
foreach ($changeset->updates() as $update) {
    echo "Row {$update->rowKey} in {$update->table}:\n";
    foreach ($update->columnDiffs as $diff) {
        echo "  {$diff->column}: {$diff->oldValue} -> {$diff->newValue}\n";
    }
}
```

## الدمج الثلاثي

هذه هي العملية الأساسية. تأخذ ثلاث لقطات حالة مسمّاة (الأساس، ونسختنا، ونسختهم) وتدمج مجموعتي التغييرات.

الأساس هو الحالة التي بدأت منها النسختان. أما نسختنا ونسختهم فهما النسختان بعد تغيّرهما بشكل مستقل، لذا يجب التقاط كل منهما من قاعدة البيانات التي تحتويها. على سبيل المثال، عندما تُنسخ بيئة اختبار من قاعدة بيانات مباشرة:

```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');      // the sandbox with our edits
Merql::init($live);
Merql::snapshot('theirs');    // live with its own changes

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

echo "Operations: {$result->operationCount()}\n";
echo "Conflicts:  {$result->conflictCount()}\n";
echo "Clean:      " . ($result->isClean() ? 'yes' : 'no') . "\n";
```

لا تلتقط لقطات الحالة الثلاث كلها من قاعدة بيانات واحدة بالتتابع. عندها ستتضمن نسختهم تغييرات نسختنا مسبقًا، وسيظهر كل صف أدرجته نسختنا على أنه تعارض `insert_insert`.

ينفّذ `Merql::merge()` الدمج على مستوى الأعمدة. ولدمج المحتوى داخل قيم TEXT وJSON أيضًا، استخدم `ThreeWayMerge` مع `CellMergeConfig`؛ راجع [الدمج على مستوى الخلية](https://merql.inline0.dev/ar/docs/merge/cell-level).

### معالجة التعارضات

عندما يغيّر الجانبان العمود نفسه إلى قيمتين مختلفتين، يثير merql تعارضًا. يمكنك حل التعارضات تلقائيًا أو فحصها يدويًا.

```php
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;

if (!$result->isClean()) {
    // Auto-resolve: one side wins.
    $resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

    // Or inspect each conflict.
    foreach ($result->conflicts() as $conflict) {
        echo "Conflict on {$conflict->table()} row {$conflict->rowKey()}:\n";
        echo "  Type:   {$conflict->type()}\n";
        echo "  Column: {$conflict->column()}\n";
        echo "  Ours:   " . var_export($conflict->oursValue(), true) . "\n";
        echo "  Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
    }
}
```

## تطبيق الدمج

بعد الحصول على نتيجة دمج نظيفة (من دون تعارضات لم تُحل)، طبّقها على قاعدة البيانات المُمرّرة إلى `Merql::init()`.

تحوّل العمليات في نتيجة الدمج الحالة الأساسية إلى الحالة المدمجة، وتشمل تغييرات الجانبين. ينفّذ `Merql::apply()` كل عملية منها، لذا تُطبّق بنجاح على قاعدة بيانات ما زالت في الحالة الأساسية. أما إذا طُبّقت على قاعدة بيانات تتضمن تغييرات أحد الجانبين بالفعل، فسيتعارض أي إدراج من ذلك الجانب مع صف موجود، ويفشل، ثم يتراجع الدمج بأكمله. لترقية تغييرات أحد الجانبين إلى قاعدة بيانات تتضمن تغييرات الجانب الآخر بالفعل (مثل نقل تعديلات بيئة الاختبار إلى قاعدة البيانات المباشرة)، طبّق العمليات الناقصة فيها فقط باستخدام [التطبيق المحروس](https://merql.inline0.dev/ar/docs/apply/guarded).

```php
$applied = Merql::apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";

if ($applied->hasErrors()) {
    foreach ($applied->errors() as $error) {
        echo "Error: {$error}\n";
    }
}
```

يضع مُطبِّق الدمج جميع العمليات ضمن معاملة. إذا فشل أي تعليمة، يُتراجع عن الدمج بأكمله ويُعاد الخطأ في `errors()`. في هذه الحالة، يظل `rowsAffected()` يحتسب التعليمات التي نُفذت قبل الفشل، رغم التراجع عنها ([#4](https://github.com/inline0/merql/issues/4))؛ تحقّق من `hasErrors()` أولًا.

## التشغيل التجريبي

عاين SQL التي سينشئها الدمج من دون تنفيذها.

```php
use Merql\Apply\DryRun;

$statements = DryRun::generate($result);

foreach ($statements as $sql) {
    echo "{$sql};\n";
}
```

## تصحيح ثنائي الاتجاه

عندما لا يتوفر لديك سوى الأساس ومجموعة تغييرات (من دون تعديلات متزامنة في «نسختنا»)، استخدم `patch()`. وهذا يعادل دمجًا لم تتغير فيه نسختنا عن الأساس.

```php
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);
```

## استخدام CLI

تتوفر كل هذه العمليات أيضًا عبر سطر الأوامر. راجع [مرجع CLI](https://merql.inline0.dev/ar/docs/cli) للاطلاع على التفاصيل كاملة.

```bash
export MERQL_DB_DSN="sqlite:mydb.sqlite"

vendor/bin/merql snapshot base
# ... change the database ...
vendor/bin/merql snapshot current
vendor/bin/merql diff base current
```

للدمج الثلاثي، التقط كل لقطة حالة من قاعدة البيانات التي تحتفظ بتلك الحالة، ثم ادمج:

```bash
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs
vendor/bin/merql merge base ours theirs --dry-run
```

## تصفية الجداول والأعمدة

يمكن استبعاد جداول من لقطات الحالة (مثل جداول ذاكرة التخزين المؤقت أو الجلسات)، وتجاهل أعمدة متغيرة (مثل الطوابع الزمنية `updated_at`).

```php
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Snapshot\Snapshotter;

$snapshotter = new Snapshotter($connection);

$snapshot = $snapshotter->capture('baseline', filters: [
    TableFilter::exclude(['sessions', 'cache_*']),
    ColumnFilter::ignore(['updated_at', 'modified_date']),
]);
```

## الخطوات التالية

- [مرجع CLI](https://merql.inline0.dev/ar/docs/cli) للاطلاع على واجهة سطر الأوامر كاملة.
- [مرجع PHP API](https://merql.inline0.dev/ar/docs/api) للاطلاع على توثيق مفصل لكل فئة وطريقة.
