跳转到正文

SQL 生成

SqlGenerator 会将 MergeResult 转换为参数化的 SQL 语句。所有值都作为参数绑定,绝不会插入到 SQL 字符串中。

PHP
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()。

PHP
use Merql\Apply\SqlGenerator;
use Merql\Snapshot\SnapshotStore;

$statements = SqlGenerator::generateGuarded(
    $selectedResult,
    SnapshotStore::load('theirs'),
);

语句格式

每条语句都是包含两个键的关联数组:

PHP
[
    'sql'    => 'INSERT INTO `posts` (`id`, `title`) VALUES (?, ?)',
    'params' => [42, 'New Title'],
]

标识符(表名和列名)使用数据库驱动程序的引用样式进行引用。MySQL 使用反引号,SQLite 使用双引号。未提供驱动程序时,默认使用反引号引用。

操作排序

操作按类型分组,并按以下顺序执行:

  1. 先执行 INSERTs -- 新行必须先存在,更新才能引用它们。
  2. 接着执行 UPDATEs。
  3. 最后执行 DELETEs -- 删除之前必须先取消行的引用。
PHP
$statements = SqlGenerator::generate($result);
// All INSERTs, then all UPDATEs, then all DELETEs.

外键排序

提供外键依赖关系时,每组内的操作都会按拓扑顺序排序。插入和更新时先处理父表,再处理子表。删除时先处理子表,再处理父表。

PHP
$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 会自动从数据库读取外键关系:

PHP
use Merql\Apply\Applier;

$applier = new Applier($connection);
$applied = $applier->apply($result);
// FK dependencies are read from the database via the driver.

生成的 SQL 示例

INSERT

Sql
INSERT INTO `posts` (`id`, `title`, `content`, `status`) VALUES (?, ?, ?, ?)
-- params: [42, 'New Post', 'Body text', 'draft']

操作值中的所有列都会包含在内。

UPDATE

Sql
UPDATE `posts` SET `title` = ?, `status` = ? WHERE `id` = ?
-- params: ['New Title', 'publish', 42]

标识列(主键)会从 SET 子句中排除,并用于 WHERE 子句。WHERE 子句的值来自对行键的解码。

DELETE

Sql
DELETE FROM `posts` WHERE `id` = ?
-- params: [42]

WHERE 子句中只会出现标识列。

带保护条件的 UPDATE

带保护条件的更新同时包含标识列子句和预期实时行子句。

Sql
UPDATE `posts` SET `title` = ? WHERE `id` = ? AND `status` = ?
-- params: ['New Title', 42, 'draft']

如果该行不再具有预期的 status,语句将影响零行,带保护条件的应用会回滚事务。

带保护条件的 INSERT

带保护条件的插入使用 NOT EXISTS,以避免插入计划创建后才出现的标识。

Sql
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 数组。连接适配器负责绑定:

PHP
$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 实例:

PHP
use Merql\Driver\SqliteDriver;

$statements = SqlGenerator::generate(
    $result,
    driver: new SqliteDriver(),
);
// Uses double-quote quoting: "posts", "title"