Darba sākšana
Šajā pamācībā aplūkota instalēšana, savienojuma izveide ar datubāzi, momentuzņēmumu uzņemšana, atšķirību aprēķināšana un trīspusējas apvienošanas veikšana.
Instalēšana
composer require merql/merql
merql nepieciešams PHP 8.2+ ar ext-mbstring un ext-zlib (abas vajadzīgas
pitmaster/pitmaster atkarībai). Jums nepieciešams arī paplašinājums izmantotajam
adapterim: ext-pdo ar ext-pdo_sqlite vai ext-pdo_mysql PDO adapterim vai ext-mysqli mysqli adapterim. Composer tās tikai iesaka,
tāpēc pārbaudiet, vai vajadzīgais paplašinājums ir instalēts.
Pakotne instalē CLI kā vendor/bin/merql. Izmantojiet merql versiju 0.4.1 vai jaunāku, lai iegūtu CLI; iepriekšējās versijās binārais fails nevarēja atrast autoloader, kad merql bija instalēts kā atkarība.
Savienojuma izveide ar datubāzi
merql darbojas, izmantojot nelielu savienojuma adaptera saskarni. Iebūvētie adapteri ir pieejami PDO un mysqli, un iebūvētie dialektu draiveri — MySQL un 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() atver jebkuru PDO atbalstītu DSN, taču merql ietver dialektu draiverus tikai MySQL un SQLite. Izmantojot citu sistēmu, piemēram, pgsql:, savienojums tiks atvērts, bet Merql::init() izraisīs Unsupported database driver: pgsql, līdz reģistrēsiet tai draiveri. Skatiet sadaļu Datubāzu draiveri.
Momentuzņēmuma izveide
Momentuzņēmumā tiek saglabātas katras tabulas visas rindas (vai tabulu apakškopa) kā pirkstu nospiedumi un dati. Pirkstu nospiedumi ļauj ātri noteikt izmaiņas; pilni rindu dati ir pieejami, kad nepieciešams aprēķināt atšķirības.
use Merql\Merql;
Merql::init($connection);
// Snapshot all tables.
$snapshot = Merql::snapshot('baseline');
// Snapshot specific tables.
$snapshot = Merql::snapshot('baseline', ['posts', 'comments', 'users']);
Momentuzņēmumi pēc noklusējuma tiek saglabāti kā JSON faili mapē .merql/snapshots/. Šo atrašanās vietu var mainīt:
use Merql\Snapshot\SnapshotStore;
SnapshotStore::setDirectory('/var/data/merql-snapshots');
Atšķirību aprēķināšana
Atšķirību aprēķins salīdzina divus momentuzņēmumus un atgriež izmaiņu kopu ar ievietošanas, atjaunināšanas un dzēšanas darbībām. Abiem momentuzņēmumiem jau jābūt izveidotiem; šeit current ir otrais momentuzņēmums, kas uzņemts pēc datubāzes izmaiņām.
$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";
}
}
Trīspusēja apvienošana
Galvenā darbība. Ņem trīs nosauktus momentuzņēmumus (base, ours, theirs) un apvieno abas izmaiņu kopas.
Base ir stāvoklis, no kura sāka abas kopijas. Ours un theirs ir abas kopijas pēc neatkarīgām izmaiņām, tāpēc katra jāiegūst no datubāzes, kurā tā atrodas. Piemēram, ja smilškaste tika izveidota, kopējot reāllaika datubāzi:
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";
Neveidojiet visus trīs momentuzņēmumus secīgi no vienas datubāzes. Tādā gadījumā theirs jau ietvertu ours izmaiņas, un katra ours ievietotā rinda tiktu uzrādīta kā insert_insert konflikts.
Merql::merge() apvieno kolonnu līmenī. Lai apvienotu arī TEXT un JSON vērtību saturu, izmantojiet ThreeWayMerge kopā ar CellMergeConfig; skatiet sadaļu Šūnu līmeņa apvienošana.
Konfliktu risināšana
Ja abas puses maina vienu un to pašu kolonnu uz atšķirīgām vērtībām, merql ziņo par konfliktu. Konfliktus var atrisināt automātiski vai pārbaudīt manuāli.
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";
}
}
Apvienošanas lietošana
Kad apvienošanas rezultātā vairs nav neatrisinātu konfliktu, lietojiet to datubāzē, kas nodota Merql::init().
Apvienošanas rezultātā iekļautās darbības pārveido base par apvienoto stāvokli, ietverot abu pušu izmaiņas. Merql::apply() izpilda tās visas, tāpēc darbības var veiksmīgi lietot datubāzē, kurā joprojām ir base stāvoklis. Lietojot tās datubāzē, kurā jau ir vienas puses izmaiņas, šīs puses ievietošana mēģinās pievienot jau esošu rindu, neizdosies un atcels visu apvienošanu. Lai pārnestu vienas puses izmaiņas datubāzē, kurā jau ir otras puses izmaiņas (piemēram, smilškastes izmaiņas uz reāllaika datubāzi), lietojiet tikai trūkstošās darbības, izmantojot Nosacītā lietošana.
$applied = Merql::apply($result);
echo "Rows affected: {$applied->rowsAffected()}\n";
if ($applied->hasErrors()) {
foreach ($applied->errors() as $error) {
echo "Error: {$error}\n";
}
}
Lietotājs ietver visas darbības transakcijā. Ja kāds priekšraksts neizdodas, visa apvienošana tiek atcelta, un kļūda tiek atgriezta laukā errors(). Šādā gadījumā rowsAffected() joprojām uzskaita priekšrakstus, kas izpildīti pirms kļūmes, lai gan tie tika atcelti (#4); vispirms pārbaudiet hasErrors().
Izmēģinājuma izpilde
Priekšskatiet SQL, ko ģenerētu apvienošana, to neizpildot.
use Merql\Apply\DryRun;
$statements = DryRun::generate($result);
foreach ($statements as $sql) {
echo "{$sql};\n";
}
Divpusējs ielāps
Ja pieejams tikai base un izmaiņu kopa (bez vienlaicīgām "ours" izmaiņām), izmantojiet patch(). Tas ir līdzvērtīgi apvienošanai, kurā ours nav mainījies salīdzinājumā ar base.
$result = Merql::patch('base', 'changes');
// Always clean: no concurrent edits means no conflicts.
Merql::apply($result);
CLI izmantošana
Visas šīs darbības pieejamas arī komandrindā. Pilnu informāciju skatiet CLI atsaucē.
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
Trīspusējai apvienošanai iegūstiet katru momentuzņēmumu no datubāzes ar atbilstošo stāvokli un pēc tam veiciet apvienošanu:
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
Tabulu un kolonnu filtrēšana
Momentuzņēmumos var izslēgt tabulas (piemēram, kešatmiņas vai sesiju tabulas) un ignorēt mainīgas kolonnas (piemēram, updated_at laika zīmogus).
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ākamās darbības
- Pilnu komandrindas saskarnes atsauci skatiet CLI atsaucē.
- Detalizētu visu klašu dokumentāciju skatiet PHP API atsaucē.