SQL 生成
SqlGenerator 会将 MergeResult 转换为参数化的 SQL 语句。所有值都作为参数绑定,绝不会插入到 SQL 字符串中。
use Merql\Apply\SqlGenerator;
$statements = SqlGenerator::generate($result);
foreach ($statements as $stmt) {
echo $stmt['sql'] . "\n";
// INSERT INTO `posts` (`id`, `title`, `status`) VALUES (?, ?, ?)
print_r($stmt['params']);
// [42, 'New Title', 'publish']
}
当经过审核的合并计划仅应在实时行仍与预期快照匹配时才应用,请使用 generateGuarded()。
use Merql\Apply\SqlGenerator;
use Merql\Snapshot\SnapshotStore;
$statements = SqlGenerator::generateGuarded(
$selectedResult,
SnapshotStore::load('theirs'),
);
语句格式
每条语句都是包含两个键的关联数组:
[
'sql' => 'INSERT INTO `posts` (`id`, `title`) VALUES (?, ?)',
'params' => [42, 'New Title'],
]
标识符(表名和列名)使用数据库驱动程序的引用样式进行引用。MySQL 使用反引号,SQLite 使用双引号。未提供驱动程序时,默认使用反引号引用。
操作排序
操作按类型分组,并按以下顺序执行:
- 先执行 INSERTs -- 新行必须先存在,更新才能引用它们。
- 接着执行 UPDATEs。
- 最后执行 DELETEs -- 删除之前必须先取消行的引用。
$statements = SqlGenerator::generate($result);
// All INSERTs, then all UPDATEs, then all DELETEs.
外键排序
提供外键依赖关系时,每组内的操作都会按拓扑顺序排序。插入和更新时先处理父表,再处理子表。删除时先处理子表,再处理父表。
$fkDependencies = [
'comments' => ['posts'], // comments references posts
'post_meta' => ['posts'], // post_meta references posts
];
$statements = SqlGenerator::generate($result, fkDependencies: $fkDependencies);
// INSERTs: posts first, then comments and post_meta
// DELETEs: comments and post_meta first, then posts
ForeignKeyResolver 会对依赖关系图执行拓扑排序。它会检测并打破循环依赖,以防止无限循环。
自动检测外键
Applier 会自动从数据库读取外键关系:
use Merql\Apply\Applier;
$applier = new Applier($connection);
$applied = $applier->apply($result);
// FK dependencies are read from the database via the driver.
生成的 SQL 示例
INSERT
INSERT INTO `posts` (`id`, `title`, `content`, `status`) VALUES (?, ?, ?, ?)
-- params: [42, 'New Post', 'Body text', 'draft']
操作值中的所有列都会包含在内。
UPDATE
UPDATE `posts` SET `title` = ?, `status` = ? WHERE `id` = ?
-- params: ['New Title', 'publish', 42]
标识列(主键)会从 SET 子句中排除,并用于 WHERE 子句。WHERE 子句的值来自对行键的解码。
DELETE
DELETE FROM `posts` WHERE `id` = ?
-- params: [42]
WHERE 子句中只会出现标识列。
带保护条件的 UPDATE
带保护条件的更新同时包含标识列子句和预期实时行子句。
UPDATE `posts` SET `title` = ? WHERE `id` = ? AND `status` = ?
-- params: ['New Title', 42, 'draft']
如果该行不再具有预期的 status,语句将影响零行,带保护条件的应用会回滚事务。
带保护条件的 INSERT
带保护条件的插入使用 NOT EXISTS,以避免插入计划创建后才出现的标识。
INSERT INTO `posts` (`id`, `title`) SELECT ?, ? WHERE NOT EXISTS (
SELECT 1 FROM `posts` WHERE `id` = ?
)
-- params: [42, 'New Post', 42]
NULL 处理
NULL 值会作为 PHP null 传入 params 数组。连接适配器负责绑定:
$statements = SqlGenerator::generate($result);
// params might contain: [42, null, 'draft']
// The adapter binds null as SQL NULL
在试运行输出中,NULL 值会显示为不带引号的字面字符串 NULL。
标识列检测
SqlGenerator 会检查基准快照的 TableSnapshot::identityColumns,以确定哪些列构成行标识。这些列用于 UPDATE 和 DELETE 语句的 WHERE 子句,并从 UPDATE 的 SET 子句中排除。
如果没有基准快照,则使用 values 数组中的第一列作为后备值。如果 values 数组为空,则假定为 id。
传入驱动程序
要控制标识符的引用方式,请传入一个 Driver 实例:
use Merql\Driver\SqliteDriver;
$statements = SqlGenerator::generate(
$result,
driver: new SqliteDriver(),
);
// Uses double-quote quoting: "posts", "title"