Saltar al contenido

Generación de SQL

SqlGenerator convierte un MergeResult en instrucciones SQL parametrizadas. Todos los valores se enlazan como parámetros; nunca se interpolan en la cadena 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']
}

Use generateGuarded() cuando un plan de fusión revisado solo deba aplicarse si las filas en vivo aún coinciden con una instantánea esperada.

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

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

Formato de las instrucciones

Cada instrucción es una matriz asociativa con dos claves:

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

Los identificadores (nombres de tablas y columnas) se entrecomillan según el estilo del controlador de base de datos. MySQL usa acentos graves; SQLite usa comillas dobles. Si no se proporciona ningún controlador, se usan acentos graves de forma predeterminada.

Orden de las operaciones

Las operaciones se agrupan por tipo y se ejecutan en este orden:

  1. Primero, INSERTs: las filas nuevas deben existir antes de que las actualizaciones puedan hacer referencia a ellas.
  2. Después, UPDATEs.
  3. Por último, DELETEs: hay que eliminar las referencias a las filas antes de borrarlas.
PHP
$statements = SqlGenerator::generate($result);
// All INSERTs, then all UPDATEs, then all DELETEs.

Orden de las claves foráneas

Cuando se proporcionan dependencias de claves foráneas, las operaciones de cada grupo se ordenan topológicamente. Las tablas principales se procesan antes que las secundarias en las inserciones y actualizaciones. En las eliminaciones, las tablas secundarias se procesan antes que las principales.

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 realiza una ordenación topológica del grafo de dependencias. Detecta las dependencias circulares y las interrumpe para evitar bucles infinitos.

Detección automática de claves foráneas

Applier lee automáticamente las relaciones de claves foráneas de la base de datos:

PHP
use Merql\Apply\Applier;

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

Ejemplos de SQL generadas

INSERT

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

Se incluyen todas las columnas de los valores de la operación.

UPDATE

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

Las columnas de identidad (clave principal) se excluyen de la cláusula SET y se usan en la cláusula WHERE. Los valores de WHERE se obtienen al decodificar la clave de la fila.

DELETE

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

En la cláusula WHERE solo aparecen columnas de identidad.

UPDATE con protección

Las actualizaciones con protección incluyen tanto las cláusulas de identidad como las cláusulas de la fila en vivo esperada.

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

Si la fila ya no tiene el status esperado, la instrucción no afecta ninguna fila y la aplicación con protección revierte la transacción.

INSERT con protección

Las inserciones con protección usan NOT EXISTS para evitar insertar una identidad que haya aparecido desde que se creó el plan.

Sql
INSERT INTO `posts` (`id`, `title`) SELECT ?, ? WHERE NOT EXISTS (
  SELECT 1 FROM `posts` WHERE `id` = ?
)
-- params: [42, 'New Post', 42]

Gestión de NULL

Los valores NULL se pasan como PHP null en la matriz params. El adaptador de conexión gestiona el enlace:

PHP
$statements = SqlGenerator::generate($result);
// params might contain: [42, null, 'draft']
// The adapter binds null as SQL NULL

En la salida de una simulación, los valores NULL se muestran como la cadena literal NULL, sin comillas.

Detección de columnas de identidad

SqlGenerator determina qué columnas conforman la identidad de la fila consultando TableSnapshot::identityColumns de la instantánea base. Estas columnas se usan en las cláusulas WHERE de las instrucciones UPDATE y DELETE, y se excluyen de las cláusulas SET de las instrucciones UPDATE.

Si no hay una instantánea base disponible, se usa como alternativa la primera columna de la matriz values. Si la matriz values está vacía, se supone que es id.

Cómo pasar un controlador

Para controlar el entrecomillado de los identificadores, pase una instancia de Driver:

PHP
use Merql\Driver\SqliteDriver;

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