Kom godt i gang
Denne vejledning gennemgår installation, tilslutning til en database, oprettelse af øjebliksbilleder, beregning af diff og udførelse af en trevejsfletning.
Installation
composer require merql/merql
merql kræver PHP 8.2+ med ext-mbstring og ext-zlib (begge kræves af afhængigheden pitmaster/pitmaster). Du skal også bruge udvidelsen til den adapter, du bruger: ext-pdo med ext-pdo_sqlite eller ext-pdo_mysql til PDO-adapteren eller ext-mysqli til mysqli-adapteren. Composer foreslår kun disse, så kontrollér, at den nødvendige udvidelse er installeret.
Pakken installerer CLI som vendor/bin/merql. Brug merql 0.4.1 eller nyere til CLI; i tidligere versioner kunne programfilen ikke finde autoloaderen, når merql var installeret som afhængighed.
Opret forbindelse til en database
merql fungerer via et lille interface til forbindelsesadaptere. Der medfølger indbyggede adaptere til PDO og mysqli samt indbyggede dialektdrivere til MySQL og SQLite.
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() åbner alle DSN'er, som PDO understøtter, men merql leveres kun med dialektdrivere til MySQL og SQLite. Med en anden databasemotor, f.eks. pgsql:, oprettes forbindelsen, men Merql::init() udløser Unsupported database driver: pgsql, indtil du registrerer en driver til den. Se Databasedrivere.
Oprettelse af et øjebliksbillede
Et øjebliksbillede registrerer hver række i hver tabel (eller et udvalg af tabeller) som fingeraftryk og data. Fingeraftryk muliggør hurtig registrering af ændringer; alle rækkedata er tilgængelige, når der er brug for en diff.
use Merql\Merql;
Merql::init($connection);
// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');
// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);
Som standard gemmes øjebliksbilleder som JSON-filer i .merql/snapshots/. Du kan ændre denne placering:
use Merql\Snapshot\SnapshotStore;
SnapshotStore::setDirectory('/var/data/merql-snapshots');
Beregning af en diff
En diff sammenligner to øjebliksbilleder og returnerer et ændringssæt med indsættelser, opdateringer og sletninger. Begge øjebliksbilleder skal allerede findes; her er current et andet øjebliksbillede, der er taget, efter at databasen blev ændret.
$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";
}
}
Trevejsfletning
Den centrale handling. Tager tre navngivne øjebliksbilleder (base, ours, theirs) og fletter de to sæt ændringer.
Base er den tilstand, som begge kopier startede fra. Ours og theirs er de to kopier efter uafhængige ændringer, så hver skal hentes fra den database, der indeholder den. Eksempel: en sandkasse blev kopieret fra en live-database:
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";
Tag ikke alle tre øjebliksbilleder fra én database i rækkefølge. Theirs ville så allerede indeholde ændringerne fra ours, og hver række, som ours indsatte, ville blive vist som en insert_insert-konflikt.
Merql::merge() fletter på kolonneniveau. Hvis du også vil flette inde i værdier af typen TEXT og JSON, skal du bruge ThreeWayMerge med en CellMergeConfig; se Fletning på celleniveau.
Håndtering af konflikter
Når begge sider ændrer samme kolonne til forskellige værdier, opstår der en konflikt i merql. Du kan løse konflikter automatisk eller undersøge dem manuelt.
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";
}
}
Anvendelse af fletningen
Når du har et rent fletningsresultat (uden uløste konflikter), skal du anvende det på databasen, der er angivet til Merql::init().
Handlingerne i et fletningsresultat omdanner base til den flettede tilstand og omfatter ændringerne fra begge sider. Merql::apply() udfører dem alle, så resultatet kan anvendes direkte på en database, der stadig er i grundtilstanden. Hvis det anvendes på en database, der allerede indeholder ændringer fra den ene side, vil en indsættelse fra den side ramme en eksisterende række, fejle og rulle hele fletningen tilbage. Hvis du vil overføre ændringer fra den ene side til en database, der allerede indeholder ændringerne fra den anden (f.eks. sandkasseændringer til live), skal du kun anvende de handlinger, den mangler, med Beskyttet anvendelse.
$applied = Merql::apply($result);
echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
foreach ($applied->errors() as $error) {
echo "Error: {$error}\n";
}
}
Anvenderen indkapsler alle handlinger i en transaktion. Hvis en sætning fejler, rulles hele fletningen tilbage, og fejlen returneres i errors(). I så fald tæller rowsAffected() stadig de sætninger, der blev kørt før fejlen, selvom de blev rullet tilbage (#4); kontrollér hasErrors() først.
Prøvekørsel
Få vist de SQL, som en fletning ville generere, uden at udføre dem.
use Merql\Apply\DryRun;
$statements = DryRun::generate($result);
foreach ($statements as $sql) {
echo "{$sql};\n";
}
Tovejspatch
Når du kun har en base og et sæt ændringer (uden samtidige ændringer fra "ours"), skal du bruge patch(). Det svarer til en fletning, hvor ours er uændret i forhold til base.
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);
Brug af CLI
Alle disse handlinger er også tilgængelige fra kommandolinjen. Se CLI-reference for alle detaljer.
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
Ved en trevejsfletning skal du hente hvert øjebliksbillede fra databasen, der indeholder den pågældende tilstand, og derefter flette:
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
Filtrering af tabeller og kolonner
Øjebliksbilleder kan udelade tabeller (f.eks. cache- eller sessionstabeller) og ignorere flygtige kolonner (f.eks. tidsstempler af typen updated_at).
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']),
]);
Næste trin
- CLI-reference med hele kommandolinjegrænsefladen.
- PHP API-reference med detaljeret dokumentation af hver klasse og metode.