简介
merql 是一个纯 PHP 库,可对数据库状态执行三方合并。它会创建表快照、计算快照之间的变更集、基于共同基线合并两组变更,并检测列级冲突。它采用 git 处理源文件时使用的同一算法,但处理对象是数据库的行和列,而不是文本行。
无需外部工具。通过可插拔的连接适配器和方言驱动程序支持 MySQL 和 SQLite。
快速示例
合并需要同一数据的三种状态:两个副本共同起始的基线,以及它们各自独立变更后的两个副本。这里,沙盒是从线上数据库复制而来,此后两者都经过了编辑。
use Merql\Connection;
use Merql\Merql;
$live = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');
// Base: live at the moment the sandbox was copied from it.
Merql::init($live);
Merql::snapshot('base');
// ... copy live.sqlite to sandbox.sqlite, then both databases change ...
// Ours: the sandbox with our edits. Theirs: live with its own changes.
Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');
$result = Merql::merge('base', 'ours', 'theirs');
if ($result->isClean()) {
foreach ($result->operations() as $operation) {
echo "{$operation->type} {$operation->table} #{$operation->rowKey} ({$operation->source})\n";
}
} else {
echo "Conflicts: {$result->conflictCount()}\n";
}
每个快照都从保存相应状态的数据库中获取。依次从同一个数据库获取全部三个快照,无法体现两个分叉的副本:对方的状态会包含我方的变更,而我方插入的每一行都会被报告为 insert_insert 冲突。
结果会列出将基线转换为合并状态所需的操作,涵盖双方变更,并为每项标注来源(ours、theirs 或 merged)。若要将沙盒中的变更写入线上数据库,请使用 受保护应用,应用线上数据库尚未包含的操作。
也可以从 CLI 获取,它会连接到每个 snapshot 在 MERQL_DB_DSN 中指定的数据库:
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
# ... copy live.sqlite to sandbox.sqlite, then both databases change ...
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
为什么选择 merql
大多数用于比较数据库状态的工具都在行级别操作:如果同一行的任意列在两个版本间不同,整行就会被标记为冲突。两个人编辑同一行的不同列时,这会造成误报冲突。
merql 在列级别进行合并。如果一方修改了 title,另一方修改了 status,这些变更可以无冲突地合并。只有双方将同一列改成不同值时才会产生冲突。
对于 TEXT 和 JSON 列,merql 可以进一步执行单元格级合并。文本列中不同文本行的两处编辑,或 JSON 列中不同键的两处编辑,都可以使用同一三方算法顺利合并。单元格级合并需显式启用:向 ThreeWayMerge 传入 CellMergeConfig。Merql 门面和 CLI 仅在列级别进行合并。参见 单元格级合并。
合并模型
Base (snapshot at fork time)
/ \
Ours (one copy's changes) Theirs (the other copy's changes)
\ /
--------- MERGE ----------
|
Merged result
+ conflicts (if any)
三个状态,与 git 相同。基线是共同祖先。我方和对方是分叉后各自的副本,分别从各自的数据库中获取。合并规则对双方一视同仁。这些名称会出现在每项操作的 source、冲突类型(update_delete 表示我方更新、对方删除)以及 OursWins 和 TheirsWins 冲突解决策略中。没有共同基线,就无法区分“新增”和“未变”。
合并本身不会写入任何内容。合并操作会将基线转换为合并状态,因此 Merql::apply() 只有在仍保存基线的数据库上运行时,才能顺利执行全部操作。若要将一方的变更带入已保存另一方变更的数据库(例如将沙盒编辑内容发布到线上),只应用该数据库尚缺少的操作;参见 受保护应用。
要求
- PHP 8.2 或更高版本。
ext-mbstring和ext-zlib,由提供文本差异功能的pitmaster/pitmaster依赖项所需。- 所用适配器对应的数据库扩展:
ext-pdo搭配ext-pdo_sqlite或ext-pdo_mysql,或者使用ext-mysqli。merql 的composer.json仅会建议安装ext-pdo和ext-mysqli,因此 Composer 不会检查它们。 - MySQL 或 SQLite 数据库。其他数据库引擎需要自定义驱动程序。
它不是什么
merql 不是架构迁移工具。它不会复制数据库,也不会实时同步数据库。它只解决一个特定问题:给定三个数据库状态,生成一个已标明冲突的合并后第四种状态。