PHPackages                             merql/merql - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. merql/merql

ActiveLibrary[Database &amp; ORM](/categories/database)

merql/merql
===========

Pure PHP three-way database merge with column-level conflict resolution. Git-style merge semantics applied to MySQL and SQLite tables.

v0.2.0(3w ago)12MITPHPPHP ^8.2CI passing

Since Apr 14Pushed 3d agoCompare

[ Source](https://github.com/inline0/merql)[ Packagist](https://packagist.org/packages/merql/merql)[ Docs](https://github.com/inline0/merql)[ RSS](/packages/merql-merql/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (4)Versions (4)Used By (0)

    ![Merql](./docs/public/logo-light.svg)

 Pure PHP three-way database merge with column-level conflict resolution

 [![CI](https://github.com/inline0/merql/actions/workflows/ci.yml/badge.svg)](https://github.com/inline0/merql/actions/workflows/ci.yml) [![Packagist](https://camo.githubusercontent.com/03582330317b0b39891b73b650292b09ec148d1c8acfcc1febf28c28d4086688/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d6572716c2f6d6572716c2e737667)](https://packagist.org/packages/merql/merql) [![license](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](https://github.com/inline0/merql/blob/main/LICENSE)

---

What is Merql?
--------------

[](#what-is-merql)

Merql is a pure PHP three-way database merge engine. It takes three database states (base, ours, theirs), computes changesets, and produces a merged result with conflict detection. Git-style merge semantics applied to MySQL and SQLite tables, at the column and cell level.

**The problem:** when two systems independently modify the same database, reconciling the changes requires understanding what each side added, changed, and deleted relative to a common ancestor. Without a common base, you cannot distinguish "added" from "unchanged."

**Merql solves this** by applying the same three-way merge algorithm that git uses for files, but operating on rows and columns instead of lines:

- Snapshot database state with row fingerprinting for fast change detection
- Compute per-column changesets between any two snapshots
- Three-way merge with column-level conflict resolution
- UI-ready merge plans with stable operation and change group IDs
- Selected operation apply for staging workflows
- Rollback plan generation with drift checks
- Cell-level merge for TEXT (line-by-line via Myers diff) and JSON (key-by-key) columns
- Parameterized SQL generation with FK-aware ordering
- Guarded apply with optimistic live-row preconditions
- Pluggable database drivers for MySQL, SQLite, and any PDO-supported engine

Quick Start
-----------

[](#quick-start)

```
composer require merql/merql
```

```
use Merql\Connection;
use Merql\Merql;

// Initialize with any PDO connection.
Merql::init(Connection::sqlite('/path/to/db.sqlite'));

// Capture database state at key points.
Merql::snapshot('base');
// ... ours makes changes ...
Merql::snapshot('ours');
// ... theirs makes changes ...
Merql::snapshot('theirs');

// Three-way merge.
$result = Merql::merge('base', 'ours', 'theirs');

if ($result->isClean()) {
    Merql::apply($result);
} else {
    foreach ($result->conflicts() as $conflict) {
        echo "{$conflict->table()}.{$conflict->column()}: "
            . "ours={$conflict->oursValue()}, theirs={$conflict->theirsValue()}\n";
    }
}
```

Merge Plans And Rollback
------------------------

[](#merge-plans-and-rollback)

Use merge plans when a host application needs to inspect, stage, or selectively apply database changes before writing to the target database.

```
use Merql\Plan\ChangeGroupSelection;
use Merql\Plan\SelectedMergeResultFactory;
use Merql\Rollback\RollbackPlanBuilder;
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

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

$snapshotter = new Snapshotter($pdo, identityRules: $rules);
SnapshotStore::save($snapshotter->capture('base', ['wp_options']));
SnapshotStore::save($snapshotter->capture('ours', ['wp_options']));
SnapshotStore::save($snapshotter->capture('theirs', ['wp_options']));

$plan = Merql::plan('plan-1', 'base', 'ours', 'theirs');

$selection = ChangeGroupSelection::fromIds([
    $plan->changeGroups[0]->id,
]);

$selected = (new SelectedMergeResultFactory())
    ->fromChangeGroupSelection($plan, $selection, SnapshotStore::load('base'));

$rollback = (new RollbackPlanBuilder())->build(
    'rollback-1',
    $plan,
    $selection->toOperationSelection($plan),
    liveBeforeRows: [
        $plan->operations[0]->id => $plan->operations[0]->theirsRow,
    ],
);

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

For prefixed or multi-environment tables, capture physical table names under a canonical name before merging:

```
use Merql\Snapshot\SnapshotStore;
use Merql\Snapshot\Snapshotter;
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;

$snapshotter = new Snapshotter($pdo, identityRules: new IdentityRuleSet([
    'wp_posts' => IdentityRule::primary(['ID']),
]));

SnapshotStore::save($snapshotter->captureAliased('sandbox', [
    'wp_onumia_abcd_posts' => 'wp_posts',
]));
```

PHP API
-------

[](#php-api)

```
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;
use Merql\Merge\ThreeWayMerge;
use Merql\Apply\DryRun;

// Three-way merge with cell-level merge for TEXT and JSON columns
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge($base, $ours, $theirs);

// Two-way merge (apply changes onto base, never conflicts)
$result = $merge->patch($base, $changes);

// Resolve conflicts programmatically
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Preview SQL without executing
$sql = DryRun::generate($result);
foreach ($sql as $statement) {
    echo $statement . ";\n";
}
```

How It Works
------------

[](#how-it-works)

```
         Base (common ancestor)
        /                       \
   Ours (our changes)      Theirs (their changes)
        \                       /
         ─────── MERGE ────────
                   │
            Merged result

```

Merql merges at four levels of granularity:

LevelUnitConflict whenTableWhole tableOne side adds, other removesRowRow by PKBoth insert same PKColumnColumn valueBoth change same column to different valuesCellContent within valueBoth change same line (text) or key (JSON)Column-level merge is the key advantage over naive row-level comparison. When both sides change the same row but different columns, merql resolves it cleanly:

```
Base:    { title: "Hello",     content: "Body",    status: "draft"   }
Ours:    { title: "Hello",     content: "Body v2", status: "draft"   }
Theirs:  { title: "New Title", content: "Body",    status: "publish" }
Result:  { title: "New Title", content: "Body v2", status: "publish" }

```

CLI
---

[](#cli)

```
# Set connection (SQLite)
export MERQL_DB_DSN="sqlite:/path/to/db.sqlite"

# Or MySQL
export MERQL_DB_NAME=mydb MERQL_DB_USER=root

# Snapshot, diff, merge
vendor/bin/merql snapshot base
vendor/bin/merql diff base current
vendor/bin/merql merge base ours theirs
vendor/bin/merql merge base ours theirs --dry-run
```

Documentation
-------------

[](#documentation)

The repo includes a dedicated docs app under [`docs/`](docs) that mirrors the same release/docs structure used in sibling projects.

```
cd docs
npm install
npm run dev
```

Topics covered:

- Getting started, CLI reference, and PHP API
- Three-way merge, merge plans, column-level merge, and cell-level merge
- Conflict detection and resolution
- SQL generation, guarded apply, rollback, dry run, and database drivers
- Row identity, identity rules, filters, schema validation, and testing strategy

Testing
-------

[](#testing)

Merql is validated with unit tests, integration tests against real SQLite, and an oracle-style regression corpus.

```
# PHPUnit unit + integration tests
composer test

# Oracle regression corpus
composer test:oracle

# Static analysis
composer analyse

# Coding standards
composer cs

# Full release-grade verification (analyse + cs + test + test:oracle)
composer verify
```

Current local verification baseline:

- `226` PHPUnit tests
- `530` assertions
- oracle regression summary `32/32`

Features
--------

[](#features)

CategoryFeaturesMergethree-way merge, two-way patch, merge plans, selected apply, column-level resolution, cell-level mergeCell MergeTEXT line-by-line (Myers diff via pitmaster), JSON key-by-key, custom mergersConflictsupdate/update, update/delete, delete/update, insert/insert, manual + auto resolveSQLparameterized INSERT/UPDATE/DELETE, FK-aware ordering, guarded apply, dry-run preview, transactionsDatabasesMySQL, SQLite built-in, extensible to any PDO driverIdentityprimary key, natural key, content hash, composite key support, identity rule registrySnapshotrow fingerprinting, JSON persistence, schema capture, aliased table capture, table/column/row filtersRollbackrollback plan generation, drift checks, inverse operation applyValidationschema mismatch detection, snapshot name validation, path traversal protectionArchitecture
------------

[](#architecture)

```
src/
├── Merql.php                  # Static facade (init, snapshot, diff, merge, apply)
├── Snapshot/                  # Capture database state (fingerprints + data)
├── Diff/                      # Compare two snapshots (insert/update/delete changesets)
├── Merge/                     # Three-way merge with column-level conflict resolution
├── Plan/                      # Merge plans, selections, selected merge results
├── Rollback/                  # Rollback plans, drift checks, inverse apply
├── CellMerge/                 # Cell-level merge (text, JSON, custom)
├── Apply/                     # SQL generation, dry run, FK ordering, applier
├── Driver/                    # Database driver interface (MySQL, SQLite)
├── Schema/                    # Table schema, validation, primary key resolution
├── Identity/                  # Row identity strategies (PK, natural key, hash)
├── Filter/                    # Table, column, and row filters
├── Connection.php             # PDO connection builder
└── Exceptions/                # Typed exceptions

```

Requirements
------------

[](#requirements)

- PHP 8.2+
- `ext-pdo` (built-in on virtually every PHP install)

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance97

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~34 days

Total

2

Last Release

21d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/dc81a975082b6843421de211768cfb67d8237bed4c972c9a1eaad46aac14b372?d=identicon)[dnnsjsk](/maintainers/dnnsjsk)

---

Top Contributors

[![dnnsjsk](https://avatars.githubusercontent.com/u/73965001?v=4)](https://github.com/dnnsjsk "dnnsjsk (25 commits)")

---

Tags

phpdiffsnapshotdatabasemysqlsqlitepdomergethree-way-mergeconflict-resolution

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/merql-merql/health.svg)

```
[![Health](https://phpackages.com/badges/merql-merql/health.svg)](https://phpackages.com/packages/merql-merql)
```

###  Alternatives

[clouddueling/mysqldump-php

PHP version of mysqldump cli that comes with MySQL

1.3k23.1k](/packages/clouddueling-mysqldump-php)[delight-im/db

Safe and convenient SQL database access in a driver-agnostic way

47165.7k7](/packages/delight-im-db)[popphp/pop-db

Pop Db Component for Pop PHP Framework

1815.7k12](/packages/popphp-pop-db)[riverside/php-orm

PHP ORM micro-library and query builder

121.3k](/packages/riverside-php-orm)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
