
# مرجع PHP API

مرجع شامل لجميع الأصناف والأساليب العامة في merql.

## Merql (واجهة)

`Merql\Merql` هو نقطة الدخول الثابتة للعمليات الشائعة. يغلّف الأصناف الأدنى مستوى ويدير حفظ اللقطات تلقائيًا.

```php
use Merql\Connection;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
use Merql\Merql;

$connection = Connection::sqlite(':memory:');
Merql::init($connection, identityRules: new IdentityRuleSet([
    'options' => IdentityRule::natural(['option_name']),
]));
```

### `init(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null): void`

هيّئ الواجهة باتصال قاعدة بيانات. يجب استدعاء ذلك قبل `snapshot()` أو `apply()` أو `applyGuarded()`؛ واستدعها مجددًا للتبديل بين قواعد البيانات، مثلًا لالتقاط ours وtheirs من نسختين مختلفتين. من دون `$driver`، تستدعي `DriverFactory::create()`، التي تُلقي استثناءً للمحركات غير MySQL وSQLite ما لم يُسجَّل برنامج تشغيل. مرّر برنامج تشغيل لتجاوز الاكتشاف التلقائي، أو مجموعة قواعد هوية لتجاوز هوية الصفوف لكل جدول أثناء التقاط اللقطة.

### `snapshot(string $name, array $tables = []): Snapshot`

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

```php
$snapshot = Merql::snapshot('baseline');
$snapshot = Merql::snapshot('baseline', ['posts', 'users']);
```

### `diff(string $base, string $current): Changeset`

حمّل لقطتين مسمّاتين واحسب مجموعة التغييرات بينهما. لا يتطلب `init()` لأنه يعمل انطلاقًا من اللقطات المحفوظة.

```php
$changeset = Merql::diff('before', 'after');
```

### `merge(string $base, string $ours, string $theirs): MergeResult`

دمج ثلاثي لثلاث لقطات مسمّاة. يعيد نتيجة تضم عمليات نظيفة وأي تعارضات. يجري الدمج على مستوى الأعمدة: تنشئ الواجهة `ThreeWayMerge` من دون `CellMergeConfig`، لذا لا يُطبّق الدمج على مستوى الخلايا. استخدم [`ThreeWayMerge`](#threewaymerge) مباشرةً لذلك.

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

### `patch(string $base, string $changes): MergeResult`

دمج ثنائي: تطبيق التغييرات على الأساس. يعادل `merge($base, $base, $changes)`. ينتج دائمًا نتيجة نظيفة لعدم وجود تعديلات متزامنة. يقتصر على مستوى الأعمدة مثل `merge()`.

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

### `apply(MergeResult $result): ApplyResult`

نفّذ نتيجة دمج نظيفة على قاعدة البيانات المُمررة إلى `init()`. يتطلب `init()`. يُلقي `ConflictException` إذا بقيت تعارضات غير محلولة.

تُنفَّذ كل عملية في النتيجة، لذا لا يُطبّق كامل ناتج `merge()` بسلاسة إلا على قاعدة بيانات ما زالت تحتفظ بحالة الأساس. أما قاعدة البيانات التي تحتفظ بالفعل بحالة أحد الجانبين، فطبّق عليها مجموعة العمليات التي لم تُطبّقها؛ راجع [التطبيق المحمي](https://merql.inline0.dev/ar/docs/apply/guarded).

```php
$applied = Merql::apply($result);
echo $applied->rowsAffected();
```

### `plan(string $id, string $base, string $ours, string $theirs, array $metadata = []): MergePlan`

أنشئ خطة دمج جاهزة لـUI انطلاقًا من ثلاث لقطات مسمّاة. تتضمن الخطة معرّفات عمليات ثابتة ومعرّفات مجموعات التغيير وسياق العمليات والتعارضات واختلافات المخطط وملخصًا وبيانات وصفية وتجزئة للخطة. الدمج على مستوى الأعمدة كما في `merge()`؛ مرّر نتيجة دمج الخلايا إلى `MergePlanBuilder::build()` لإجراء الدمج على مستوى الخلايا.

```php
$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs', [
    'source' => 'review-ui',
]);
```

### `applyGuarded(MergeResult $result, Snapshot $expectedLive): ApplyResult`

طبّق نتيجة دمج نظيفة مع شروط مسبقة تفاؤلية للصفوف الحية. تتطلب عمليات التحديث والحذف أن تطابق الصفوف الحية الحالية `$expectedLive`؛ وتتطلب عمليات الإدراج ألا تكون الهوية المستهدفة موجودة بالفعل. `$expectedLive` لقطة من قاعدة البيانات المستهدفة (theirs، عند ترقية ours إلى قاعدة البيانات التي أُخذت منها لقطة theirs). يؤدي أي استعلام لا يؤثر في أي صف إلى التراجع عن المعاملة.

```php
use Merql\Snapshot\SnapshotStore;

$applied = Merql::applyGuarded($selectedResult, SnapshotStore::load('theirs'));
```

### `reset(): void`

امسح نسختَي الاتصال وSnapshotter الداخليتين. يُستخدم ذلك أساسًا في الاختبارات لإعادة ضبط الحالة بين عمليات التشغيل.

---

## الاتصال

ينشئ `Merql\Connection` محوّلات اتصال بإعدادات افتراضية مناسبة. تستخدم اتصالات PDO الاستثناءات عند الخطأ ووضع الجلب الترابطي وعدم محاكاة التحضير وجلب القيم كسلاسل. يطبّع محوّل mysqli القيم المسترجعة لتطابق الشكل نفسه: سلسلة أو null.

### `mysql(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): PdoDatabaseConnection`

```php
use Merql\Connection;

$connection = Connection::mysql('127.0.0.1', 'my_app', 'root', 'secret');
```

### `mysqli(string $host, string $database, string $username, string $password = '', int $port = 3306, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

```php
$connection = Connection::mysqli('127.0.0.1', 'my_app', 'root', 'secret');
```

### `sqlite(string $path = ':memory:'): PdoDatabaseConnection`

ينشئ اتصال SQLite. ويفعّل المفاتيح الخارجية تلقائيًا عبر `PRAGMA foreign_keys = ON`.

```php
$connection = Connection::sqlite('/var/data/app.sqlite');
$connection = Connection::sqlite(':memory:');
```

### `fromDsn(string $dsn, string $username = '', string $password = ''): PdoDatabaseConnection`

اتصل باستخدام أي سلسلة DSN لـPDO. يضم merql برامج تشغيل لـ`mysql` و`sqlite` فقط؛ تفتح سلاسل DSN الأخرى اتصالًا، لكن `Merql::init()` و`Snapshotter` والمطبّقات تُلقي `Unsupported database driver` ما لم يُسجّل برنامج تشغيل باستخدام `DriverFactory::register()`.

```php
$connection = Connection::fromDsn('mysql:host=localhost;dbname=app', 'root', 'pass');
```

### `fromPdo(PDO $pdo): PdoDatabaseConnection`

غلّف نسخة PDO موجودة.

### `fromMysqli(mysqli $mysqli, string $charset = 'utf8mb4'): MysqliDatabaseConnection`

غلّف نسخة mysqli موجودة.

### عقد المحوّل

تنفّذ جميع المحوّلات `Merql\Database\DatabaseConnection`:

```php
interface DatabaseConnection
{
    public function driverName(): string;
    public function execute(string $sql, array $params = []): int;
    public function query(string $sql, array $params = []): array;
    public function scalar(string $sql, array $params = []): ?string;
    public function beginTransaction(): void;
    public function commit(): void;
    public function rollBack(): void;
    public function lastInsertId(): string;
}
```

تقبل `execute()` و`query()` و`scalar()` معاملات `list<scalar|null>` موضعية لعناصر الإحلال `?`. تعيد `query()` القيمة `list<array<string, scalar|null>>`؛ وتُطبّع القيم لتأخذ الشكل نفسه، سلسلة أو null، عبر محوّلات PDO وmysqli.

---

## ملتقط اللقطات

يلتقط `Merql\Snapshot\Snapshotter` حالة قاعدة البيانات. وعلى خلاف الواجهة، يمنحك تحكمًا كاملًا في المرشحات ولا يحفظ اللقطات تلقائيًا.

### `__construct(DatabaseConnection $connection, ?Driver $driver = null, ?IdentityRuleSet $identityRules = null)`

يُكتشف برنامج التشغيل تلقائيًا من هوية الاتصال إن لم يُحدَّد. مرّر `IdentityRuleSet` عندما ينبغي لهوية الصفوف الخاصة بكل جدول تجاوز الإعدادات الافتراضية للمخطط.

### `capture(string $name, array $tables = [], array $filters = []): Snapshot`

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

```php
use Merql\Snapshot\Snapshotter;
use Merql\Filter\TableFilter;
use Merql\Filter\ColumnFilter;
use Merql\Filter\RowFilter;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$snapshotter = new Snapshotter(
    $connection,
    identityRules: new IdentityRuleSet([
        'wp_options' => IdentityRule::natural(['option_name']),
    ]),
);

// All tables.
$snapshot = $snapshotter->capture('full');

// Specific tables with filters.
$snapshot = $snapshotter->capture('filtered', ['posts', 'users'], [
    TableFilter::exclude(['cache_*', 'sessions']),
    ColumnFilter::ignore(['updated_at', 'created_at']),
    RowFilter::create(fn(string $table, array $row) => $row['status'] !== 'trash'),
]);
```

### `captureAliased(string $name, array $physicalToCanonicalTableMap, array $filters = []): Snapshot`

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

```php
$snapshot = $snapshotter->captureAliased('sandbox', [
    'wp_onumia_abcd_posts' => 'wp_posts',
    'wp_onumia_abcd_postmeta' => 'wp_postmeta',
]);
```

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

### `fromData(string $name, array $tableData): Snapshot` (ثابتة)

أنشئ لقطة من بيانات خام دون اتصال بقاعدة بيانات. مفيد للاختبار أو عند استيراد البيانات من مصدر آخر.

```php
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$schema = new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']);

$snapshot = Snapshotter::fromData('test', [
    'posts' => new TableSnapshotData($schema, [
        ['id' => '1', 'title' => 'Hello'],
        ['id' => '2', 'title' => 'World'],
    ], ['id']),
]);
```

---

## اللقطة

`Merql\Snapshot\Snapshot` كائن قيمة للقراءة فقط يمثل حالة كاملة لقاعدة البيانات.

```php
$snapshot->name;                    // string: snapshot identifier
$snapshot->tables;                  // array<string, TableSnapshot>
$snapshot->tableNames();            // list<string>
$snapshot->hasTable('posts');       // bool
$snapshot->getTable('posts');       // ?TableSnapshot
```

---

## TableSnapshot

تحتوي `Merql\Snapshot\TableSnapshot` على صفوف جدول واحد وبصماته ومخططه.

```php
$table = $snapshot->getTable('posts');

$table->schema;                    // TableSchema
$table->identityColumns;           // list<string>: columns used for row identity
$table->fingerprints;              // array<string, string>: row key to hash
$table->rows;                      // array<string, array<string, mixed>>

$table->rowKeys();                 // list<string>
$table->rowCount();                // int
$table->hasRow('42');              // bool
$table->getRow('42');              // ?array<string, mixed>
$table->getFingerprint('42');      // ?string
```

---

## TableSnapshotData

`Merql\Snapshot\TableSnapshotData` هي بيانات إدخال لـ `Snapshotter::fromData()`.

```php
use Merql\Snapshot\TableSnapshotData;
use Merql\Schema\TableSchema;

$data = new TableSnapshotData(
    schema: new TableSchema('posts', ['id' => 'int', 'title' => 'varchar'], ['id']),
    rows: [
        ['id' => '1', 'title' => 'First Post'],
    ],
    identityColumns: ['id'],
);
```

---

## IdentityRule

تحدد `Merql\Identity\IdentityRule` الأعمدة المستخدمة لإنشاء مفاتيح الصفوف.

```php
use Merql\Identity\IdentityRule;

$primary = IdentityRule::primary(['id']);
$natural = IdentityRule::natural(['option_name']);
$composite = IdentityRule::composite(['object_id', 'term_taxonomy_id']);
$content = IdentityRule::content(['name', 'value']);

$key = $natural->key(['option_name' => 'siteurl']);
// "siteurl"
```

### `forSchema(TableSchema $schema): IdentityRule` (ثابتة)

حل قاعدة الهوية الافتراضية من المخطط: المفتاح الأساسي، ثم أول مفتاح فريد،
ثم الرجوع إلى المحتوى.

### `toArray(): array`

تسلسل القاعدة بصيغة `['type' => string, 'columns' => list<string>]`.

### `fromArray(array $data): IdentityRule` (ثابتة)

استعادة قاعدة من بيانات متسلسلة.

---

## IdentityRuleSet

تخزن `Merql\Identity\IdentityRuleSet` تجاوزات الهوية الخاصة بكل جدول.

```php
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$rules = new IdentityRuleSet([
    'wp_options' => IdentityRule::natural(['option_name']),
]);

$rule = $rules->ruleFor($schema);
```

### `with(string $table, IdentityRule $rule): IdentityRuleSet`

إرجاع مجموعة قواعد جديدة مع إضافة تجاوز لجدول واحد.

### `conflictsFor(string $table, IdentityRule $rule, array $rows): array`

إرجاع إدخالات `IdentityConflict` عندما تنتج عدة صفوف المفتاح نفسه لقاعدة ما.

### `toArray(): array`

تسلسل قواعد الجداول في مصفوفة آمنة لـ JSON.

### `fromArray(array $data): IdentityRuleSet` (ثابتة)

استعادة مجموعة قواعد من بيانات متسلسلة.

---

## SnapshotStore

تحفظ `Merql\Snapshot\SnapshotStore` اللقطات وتحملها بصيغة ملفات JSON.

### `setDirectory(string $directory): void`

تعيين مجلد التخزين. القيمة الافتراضية هي `.merql/snapshots`.

### `save(Snapshot $snapshot): void`

كتابة لقطة على القرص.

### `load(string $name): Snapshot`

تحميل لقطة حسب الاسم. يُطلق `SnapshotException` إذا لم يُعثر عليها.

### `exists(string $name): bool`

التحقق من وجود ملف لقطة.

### `delete(string $name): void`

إزالة ملف لقطة.

---

## أداة المقارنة

تقارن `Merql\Diff\Differ` بين لقطتين وتنشئ مجموعة تغييرات.

### `diff(Snapshot $base, Snapshot $current): Changeset`

```php
use Merql\Diff\Differ;

$differ = new Differ();
$changeset = $differ->diff($baseSnapshot, $currentSnapshot);
```

### `valuesEqual(mixed $a, mixed $b): bool` (ثابتة)

قارن قيمتين بتحويلهما إلى سلاسل نصية. تُعامل NULL كقيمة مستقلة: `NULL === NULL` تساوي true، و`NULL === ''` تساوي false.

---

## مجموعة التغييرات

`Merql\Diff\Changeset` مجموعة للقراءة فقط من العمليات على مستوى الصفوف.

```php
$changeset->inserts();             // list<RowInsert>
$changeset->updates();             // list<RowUpdate>
$changeset->deletes();             // list<RowDelete>
$changeset->isEmpty();             // bool
$changeset->count();               // int: total operations
$changeset->forTable('posts');     // Changeset: filtered to one table
```

---

## RowInsert, RowUpdate, RowDelete

كائنات قيم للقراءة فقط تمثل تغييرات الصفوف الفردية.

### RowInsert

```php
$insert->table;                    // string
$insert->rowKey;                   // string
$insert->values;                   // array<string, mixed>
```

### RowUpdate

```php
$update->table;                    // string
$update->rowKey;                   // string
$update->columnDiffs;              // list<ColumnDiff>
$update->fullRow;                  // array<string, mixed>: complete current row
$update->columnDiffMap();          // array<string, ColumnDiff>: keyed by column name
```

### RowDelete

```php
$delete->table;                    // string
$delete->rowKey;                   // string
$delete->oldValues;                // array<string, mixed>: values before deletion
```

---

## ColumnDiff

يمثل `Merql\Diff\ColumnDiff` تغييرًا واحدًا في عمود ضمن تحديث صف.

```php
$diff->column;                     // string
$diff->oldValue;                   // mixed
$diff->newValue;                   // mixed
```

---

## ThreeWayMerge

`Merql\Merge\ThreeWayMerge` هي خوارزمية الدمج الأساسية.

### `__construct(?CellMergeConfig $cellMergeConfig = null)`

مرر `CellMergeConfig` لتمكين الدمج على مستوى الخلايا لأعمدة TEXT وJSON.

```php
use Merql\Merge\ThreeWayMerge;
use Merql\CellMerge\CellMergeConfig;

// Default: column-level merge only.
$merge = new ThreeWayMerge();

// With cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
```

### `merge(Snapshot $base, Snapshot $ours, Snapshot $theirs): MergeResult`

إجراء دمج ثلاثي الاتجاهات. يحسب مجموعات التغييرات داخليًا (من الأساس إلى تغييراتنا، ومن الأساس إلى تغييراتهم) ثم يدمجها.

```php
$result = $merge->merge($baseSnapshot, $oursSnapshot, $theirsSnapshot);
```

### `patch(Snapshot $base, Snapshot $changes): MergeResult`

اختصار للدمج ثنائي الاتجاه. يعادل `merge($base, $base, $changes)`.

### `schemaMismatches(): array`

إرجاع `SchemaException[]` لأي اختلافات في المخطط رُصدت أثناء عملية الدمج الأخيرة (الأعمدة المضافة أو المحذوفة بين اللقطات).

---

## MergeResult

`Merql\Merge\MergeResult` كائن قيمة للقراءة فقط يحتوي على نتيجة الدمج.

```php
$result->isClean();                // bool: no conflicts
$result->operations();             // list<MergeOperation>
$result->conflicts();              // list<Conflict>
$result->operationCount();         // int
$result->conflictCount();          // int
$result->baseSnapshot();           // ?Snapshot
$result->schemaMismatches();       // list<SchemaException>
$result->hasSchemaMismatches();    // bool
```

---

## MergePlan

`Merql\Plan\MergePlan` نموذج مراجعة قابل للتسلسل لعملية دمج.

```php
$plan->id;                        // string
$plan->baseSnapshot;              // string
$plan->oursSnapshot;              // string
$plan->theirsSnapshot;            // string
$plan->summary;                   // MergePlanSummary
$plan->changeGroups;              // list<MergePlanChangeGroup>
$plan->operations;                // list<MergePlanOperation>
$plan->conflicts;                 // list<MergePlanConflict>
$plan->schemaMismatches;          // list<string>
$plan->hash;                      // string
$plan->metadata;                  // array<string, mixed>
```

استخدم `MergePlanSerializer::toJson()` و`::fromJson()` لإجراء رحلات ذهاب وإياب مستقرة لـ JSON.

## التحديدات

تخزن `ChangeGroupSelection` معرّفات مجموعات التغييرات المرحلية. حوّلها إلى `OperationSelection` عند إنشاء نتائج دمج محددة أو خطط تراجع.

```php
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;

$selection = ChangeGroupSelection::fromIds([$groupId]);
$operationSelection = $selection->toOperationSelection($plan);

$selected = (new SelectedMergeResultFactory())
    ->fromOperationSelection($plan, $operationSelection);
```

## RollbackPlan

تخزن `Merql\Rollback\RollbackPlan` العمليات العكسية وتوقعات الانحراف لعملية تطبيق محددة.

```php
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Rollback\RollbackPlanSerializer;

$rollback = (new RollbackPlanBuilder())->build(
    'rollback-1',
    $plan,
    $operationSelection,
    $liveBeforeRows,
);

$json = RollbackPlanSerializer::toJson($rollback);
```

استخدم `RollbackDrift` قبل تطبيق التراجع، ثم `RollbackPlanApplier` لتنفيذ العمليات العكسية.

---

## MergeOperation

`Merql\Merge\MergeOperation` عملية واحدة محسومة ناتجة عن الدمج.

```php
$op->type;                         // string: 'insert' | 'update' | 'delete'
$op->table;                        // string
$op->rowKey;                       // string
$op->values;                       // array<string, mixed>
$op->source;                       // string: 'ours' | 'theirs' | 'merged'
```

ثوابت الأنواع:

```php
MergeOperation::TYPE_INSERT;       // 'insert'
MergeOperation::TYPE_UPDATE;       // 'update'
MergeOperation::TYPE_DELETE;       // 'delete'
```

---

## التعارض

يمثل `Merql\Merge\Conflict` تعارض دمج لم يُحل.

```php
$conflict->table();                // string
$conflict->rowKey();               // string
$conflict->type();                 // string: 'update_update' | 'update_delete' | 'delete_update' | 'insert_insert'
$conflict->column();               // ?string (null for structural conflicts)
$conflict->oursValue();            // mixed
$conflict->theirsValue();          // mixed
$conflict->baseValue();            // mixed
$conflict->primaryKey(['id']);     // array<string, string>: decoded from rowKey
```

### أنواع التعارضات

| النوع | المعنى |
|---|---|
| `update_update` | غيّر الطرفان العمود نفسه إلى قيمتين مختلفتين |
| `update_delete` | حدّثنا صفًا حذفه الطرف الآخر |
| `delete_update` | حذفنا صفًا حدّثه الطرف الآخر |
| `insert_insert` | أدرج الطرفان صفًا لهما المفتاح الأساسي نفسه |

---

## ConflictResolver

تطبق `Merql\Merge\ConflictResolver` سياسة حل على جميع التعارضات في نتيجة الدمج.

### `resolve(MergeResult $result, ConflictPolicy $policy): MergeResult` (ثابتة)

إرجاع `MergeResult` جديد مع حل التعارضات وفقًا للسياسة.

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

// Accept all "theirs" values when conflicts arise.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Accept all "ours" values.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);

// Return unchanged (for manual inspection).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
```

---

## ConflictPolicy

`Merql\Merge\ConflictPolicy` تعداد مدعوم بثلاث حالات.

```php
use Merql\Merge\ConflictPolicy;

ConflictPolicy::OursWins;          // Resolve all conflicts in favor of "ours"
ConflictPolicy::TheirsWins;        // Resolve all conflicts in favor of "theirs"
ConflictPolicy::Manual;            // Leave conflicts unresolved for manual handling
```

---

## ColumnMerge

تنفذ `Merql\Merge\ColumnMerge` منطق الدمج لكل عمود في صف واحد. وتُستخدم داخليًا بواسطة `ThreeWayMerge`.

### `merge(string $table, string $rowKey, array $base, array $ours, array $theirs, ?CellMergeConfig $cellMergeConfig = null, array $columnTypes = []): array` (ثابتة)

إرجاع `['values' => array, 'conflicts' => list<Conflict>]`.

```php
use Merql\Merge\ColumnMerge;

$result = ColumnMerge::merge(
    'posts',
    '42',
    ['title' => 'Hello', 'status' => 'draft'],     // base
    ['title' => 'Hello', 'status' => 'published'],  // ours
    ['title' => 'New Title', 'status' => 'draft'],   // theirs
);

// $result['values'] = ['title' => 'New Title', 'status' => 'published']
// $result['conflicts'] = [] (clean merge)
```

---

## أداة التطبيق

تنفذ `Merql\Apply\Applier` نتيجة دمج على قاعدة بيانات.

### `__construct(DatabaseConnection $connection, ?Driver $driver = null)`

### `apply(MergeResult $result, ?Snapshot $base = null): ApplyResult`

تنفذ جميع العمليات ضمن معاملة واحدة. تتراجع عند حدوث أي خطأ وتُبلغ عنه في `ApplyResult::errors()` بدلًا من إطلاقه. يُطلق `ConflictException` إذا احتوت النتيجة على تعارضات لم تُحل.

```php
use Merql\Apply\Applier;

$applier = new Applier($connection);
$applied = $applier->apply($result);

echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
    print_r($applied->errors());
}
```

يُعالج ترتيب المفاتيح الخارجية تلقائيًا: تُرتب عمليات الإدراج حسب ترتيب الاعتماد، وعمليات الحذف بترتيب الاعتماد العكسي.

---

## ApplyResult

`Merql\Apply\ApplyResult` كائن قيمة للقراءة فقط تُرجعه أداة التطبيق.

```php
$applied->rowsAffected();         // int
$applied->errors();               // list<string>
$applied->hasErrors();            // bool
```

بعد التراجع عن عملية تطبيق، يظل `rowsAffected()` يحصي العبارات التي نُفذت قبل الإخفاق ([#4](https://github.com/inline0/merql/issues/4)). تحقق من `hasErrors()` قبل الاعتماد على العدد.

---

## DryRun

تنشئ `Merql\Apply\DryRun` عبارات SQL سهلة القراءة دون تنفيذ أي شيء.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (ثابتة)

إرجاع قائمة بسلاسل SQL مع تضمين القيم فيها (للعرض فقط).

```php
use Merql\Apply\DryRun;

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

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

// Output:
// INSERT INTO `posts` (`id`, `title`) VALUES ('10', 'New Post');
// UPDATE `posts` SET `title` = 'Updated' WHERE `id` = '5';
// DELETE FROM `comments` WHERE `id` = '3';
```

---

## SqlGenerator

تنشئ `Merql\Apply\SqlGenerator` عبارات SQL ذات معلمات. وتُستخدم داخليًا بواسطة كل من `Applier` و`DryRun`.

### `generate(MergeResult $result, ?Snapshot $base = null, array $fkDependencies = [], ?Driver $driver = null): array` (ثابتة)

إرجاع `list<array{sql: string, params: list<scalar|null>}>`.

```php
use Merql\Apply\SqlGenerator;

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

foreach ($statements as $stmt) {
    $connection->execute($stmt['sql'], $stmt['params']);
}
```

---

## CellMergeConfig

تهيئ `Merql\CellMerge\CellMergeConfig` استراتيجيات دمج الخلايا لأعمدة أو أنواع أعمدة محددة.

### `auto(): self` (ثابتة)

دالة إنشاء ميسّرة تعيّن `TextCellMerger` لأعمدة TEXT/LONGTEXT و`JsonCellMerger` لأعمدة JSON.

```php
use Merql\CellMerge\CellMergeConfig;

$config = CellMergeConfig::auto();
```

### `forColumn(string $column, CellMerger $merger): self`

تعيين أداة دمج لعمود محدد. استخدم `table.column` للأسماء المؤهلة أو `column` لجميع الجداول.

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\TextCellMerger;

$config = (new CellMergeConfig())
    ->forColumn('posts.content', new TextCellMerger())
    ->forColumn('description', new TextCellMerger());
```

### `forType(string $typePattern, CellMerger $merger): self`

تعيين أداة دمج لجميع الأعمدة المطابقة لنمط نوع (مطابقة غير حساسة لحالة الأحرف للسلسلة الفرعية).

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;

$config = (new CellMergeConfig())
    ->forType('json', new JsonCellMerger());
```

### `getMerger(string $table, string $column, string $columnType = ''): CellMerger`

البحث عن أداة الدمج لجدول وعمود ونوع محددين. الأولوية: اسم العمود المؤهل، ثم اسم العمود غير المؤهل، ثم نمط النوع، ثم الافتراضي (المعتم).

---

## CellMerger (واجهة)

`Merql\CellMerge\CellMerger` هي الواجهة لاستراتيجيات دمج الخلايا.

```php
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

interface CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
}
```

تتوفر ثلاثة تطبيقات مدمجة.

### TextCellMerger

تدمج `Merql\CellMerge\TextCellMerger` النصوص متعددة الأسطر باستخدام فرق ثلاثي الاتجاهات سطرًا بسطر (الخوارزمية نفسها التي يستخدمها git لمحتوى الملفات). عندما يعدّل الطرفان سطورًا مختلفة، يتم الدمج بنجاح. وعندما يعدّلان السطر نفسه بطرق مختلفة، يُبلغ عن تعارض.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
$result = $merger->merge(
    "line 1\nline 2\nline 3",
    "line 1\nline 2 modified\nline 3",
    "line 1\nline 2\nline 3 modified",
);
// $result->clean === true
// $result->value === "line 1\nline 2 modified\nline 3 modified"
```

### JsonCellMerger

تدمج `Merql\CellMerge\JsonCellMerger` قيم JSON بمقارنة المفاتيح ذات المستوى الأعلى كلًّا على حدة. يُدمج كل مفتاح باستخدام منطق ثلاثي الاتجاهات. وتُقارن الكائنات المتداخلة كسلاسل JSON معتمة (ولا يجري دمجها تكراريًا).

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
$result = $merger->merge(
    '{"a": 1, "b": 2}',
    '{"a": 10, "b": 2}',        // changed "a"
    '{"a": 1, "b": 20}',         // changed "b"
);
// $result->clean === true
// $result->value === '{"a":10,"b":20}'
```

### OpaqueCellMerger

`Merql\CellMerge\OpaqueCellMerger` هي القيمة الافتراضية. تتعامل مع القيم كسلاسل معتمة وتُبلغ دائمًا عن تعارض عند اختلافها.

---

## CellMergeResult

`Merql\CellMerge\CellMergeResult` هو نوع الإرجاع لجميع أدوات دمج الخلايا.

```php
$result->clean;                    // bool: true if merge succeeded
$result->value;                    // mixed: merged value (or ours on conflict)
$result->conflicts;                // int: number of conflicts

// Factory methods.
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // conflict with count
```

---

## TableSchema

`Merql\Schema\TableSchema` تمثيل للقراءة فقط لبنية الجدول.

```php
use Merql\Schema\TableSchema;

$schema = new TableSchema(
    name: 'posts',
    columns: ['id' => 'int', 'title' => 'varchar(255)', 'content' => 'text'],
    primaryKey: ['id'],
    uniqueKeys: [['slug']],
);

$schema->name;                     // string
$schema->columns;                  // array<string, string>: name to type
$schema->primaryKey;               // list<string>
$schema->uniqueKeys;               // list<list<string>>
$schema->hasPrimaryKey();          // bool
$schema->hasUniqueKeys();          // bool
$schema->columnNames();            // list<string>
```

---

## نظام برامج التشغيل

يستخدم merql نظام برامج تشغيل قابلاً للتوصيل لتجريد العمليات الخاصة بكل قاعدة بيانات. تُكتشف برامج التشغيل تلقائيًا من هوية برنامج تشغيل الاتصال.

### برنامج التشغيل (واجهة)

تحدد `Merql\Driver\Driver` العقد الذي تنفذه جميع برامج تشغيل قواعد البيانات.

```php
interface Driver
{
    public function quoteIdentifier(string $name): string;
    public function listTables(DatabaseConnection $connection): array;
    public function readSchema(DatabaseConnection $connection, string $table): TableSchema;
    public function readForeignKeys(DatabaseConnection $connection): array;
    public function selectAll(string $table): string;
}
```

التطبيقات المدمجة: `MysqlDriver` و`SqliteDriver`.

### DriverFactory

تكتشف `Merql\Driver\DriverFactory` برنامج التشغيل تلقائيًا من الاتصال.

```php
use Merql\Driver\DriverFactory;

$driver = DriverFactory::create($connection);
```

سجّل برنامج تشغيل مخصصًا لقواعد بيانات أخرى:

```php
DriverFactory::register('pgsql', MyPostgresDriver::class);
```

---

## المرشحات

### TableFilter

تضمّن `Merql\Filter\TableFilter` الجداول أو تستبعدها باستخدام أنماط glob.

```php
use Merql\Filter\TableFilter;

$filter = TableFilter::exclude(['cache_*', 'sessions', 'temp_*']);
$filter = TableFilter::include(['posts', 'users', 'comments']);
```

### ColumnFilter

تزيل `Merql\Filter\ColumnFilter` أعمدة محددة من اللقطات.

```php
use Merql\Filter\ColumnFilter;

$filter = ColumnFilter::ignore(['updated_at', 'created_at', 'modified_date']);
```

### RowFilter

تصفّي `Merql\Filter\RowFilter` الصفوف باستخدام دالة شرطية.

```php
use Merql\Filter\RowFilter;

$filter = RowFilter::create(function (string $table, array $row): bool {
    // Exclude trashed posts.
    if ($table === 'posts' && ($row['status'] ?? '') === 'trash') {
        return false;
    }
    return true;
});
```

---

## الاستثناءات

### SnapshotException

يُطلق `Merql\Exceptions\SnapshotException` عندما يتعذر العثور على لقطة أو يكون اسمها غير صالح.

```php
SnapshotException::notFound('baseline', '/path/to/baseline.json');
```

### MergeException

يُطلق `Merql\Exceptions\MergeException` عند غياب لقطة لازمة لإجراء الدمج.

```php
MergeException::snapshotRequired('base');
```

### ConflictException

يُطلق `Merql\Exceptions\ConflictException` عند محاولة تطبيق نتيجة دمج تحتوي على تعارضات لم تُحل.

```php
ConflictException::unresolved(3);
// Message: "Cannot apply merge with 3 unresolved conflict(s)"
```

### SchemaException

يُطلق `Merql\Exceptions\SchemaException` (أو يُجمع) عند اختلاف المخططات بين اللقطات.

```php
SchemaException::mismatch('posts', 'column "tags" exists in current but not in base');
```

لا توقف حالات عدم تطابق المخطط عملية الدمج. بل تُجمع وتُتاح عبر `$result->schemaMismatches()` لفحصها.
