Vai al contenuto

Generazione di SQL

SqlGenerator converte un MergeResult in istruzioni SQL parametrizzate. Tutti i valori vengono passati come parametri, mai interpolati nella stringa 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']
}

Usate generateGuarded() quando un piano di fusione revisionato deve essere applicato solo se le righe live corrispondono ancora a uno snapshot atteso.

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

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

Formato delle istruzioni

Ogni istruzione è un array associativo con due chiavi:

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

Gli identificatori (nomi di tabelle e colonne) vengono quotati secondo lo stile del driver del database. MySQL usa i backtick, SQLite usa le virgolette doppie. Se non viene specificato un driver, per impostazione predefinita si usano i backtick.

Ordine delle operazioni

Le operazioni sono raggruppate per tipo ed eseguite in questo ordine:

  1. Prima gli INSERT: le nuove righe devono esistere prima che gli aggiornamenti possano farvi riferimento.
  2. Poi gli UPDATE.
  3. Infine i DELETE: prima di eliminare le righe è necessario rimuovere i riferimenti a esse.
PHP
$statements = SqlGenerator::generate($result);
// All INSERTs, then all UPDATEs, then all DELETEs.

Ordinamento delle chiavi esterne

Quando vengono fornite le dipendenze delle chiavi esterne, le operazioni all'interno di ogni gruppo vengono ordinate topologicamente. Per gli inserimenti e gli aggiornamenti, le tabelle padre vengono elaborate prima di quelle figlie. Per le eliminazioni, le tabelle figlie vengono elaborate prima di quelle padre.

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 esegue un ordinamento topologico sul grafo delle dipendenze. Le dipendenze circolari vengono rilevate e interrotte per evitare cicli infiniti.

Rilevamento automatico delle chiavi esterne

Applier legge automaticamente dal database le relazioni tra chiavi esterne:

PHP
use Merql\Apply\Applier;

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

Esempi di SQL generati

INSERT

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

Vengono incluse tutte le colonne dei valori dell'operazione.

UPDATE

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

Le colonne di identità (chiave primaria) sono escluse dalla clausola SET e usate nella clausola WHERE. I valori della clausola WHERE provengono dalla decodifica della chiave della riga.

DELETE

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

Nella clausola WHERE compaiono solo le colonne di identità.

UPDATE protetto

Gli aggiornamenti protetti includono sia le clausole di identità sia quelle per le righe correnti attese.

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

Se la riga non ha più status previsto, l'istruzione interessa zero righe e l'applicazione protetta annulla la transazione.

INSERT protetto

Gli inserimenti protetti usano NOT EXISTS per evitare di inserire una riga con un'identità già comparsa dopo la creazione del piano.

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

Gestione di NULL

I valori NULL vengono passati come PHP null nell'array dei parametri. L'adapter della connessione gestisce il binding:

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

Nell'output dell'esecuzione di prova, i valori NULL vengono visualizzati come stringa letterale NULL senza virgolette.

Rilevamento delle colonne di identità

SqlGenerator determina quali colonne costituiscono l'identità della riga esaminando TableSnapshot::identityColumns dello snapshot di base. Queste colonne vengono usate nelle clausole WHERE delle istruzioni UPDATE e DELETE ed escluse dalle clausole SET degli UPDATE.

Se non è disponibile uno snapshot di base, viene usata come fallback la prima colonna dell'array dei valori. Se l'array dei valori è vuoto, si presume id.

Passare un driver

Per controllare la quotatura degli identificatori, passate un'istanza di Driver:

PHP
use Merql\Driver\SqliteDriver;

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