
# Δημιουργία 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 χρησιμοποιεί backticks, ενώ το SQLite χρησιμοποιεί διπλά εισαγωγικά. Όταν δεν παρέχεται οδηγός, τα backticks χρησιμοποιούνται ως προεπιλογή.

## Σειρά ενεργειών

Οι ενέργειες ομαδοποιούνται ανά τύπο και εκτελούνται με την εξής σειρά:

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` στον πίνακα παραμέτρων. Η σύνδεση διαχειρίζεται τη δέσμευση:

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

Στην έξοδο δοκιμαστικής εκτέλεσης, οι τιμές NULL εμφανίζονται ως η κυριολεκτική συμβολοσειρά `NULL` χωρίς εισαγωγικά.

## Εντοπισμός στηλών ταυτότητας

Το `SqlGenerator` καθορίζει ποιες στήλες σχηματίζουν την ταυτότητα της γραμμής εξετάζοντας το `TableSnapshot::identityColumns` του βασικού στιγμιότυπου. Αυτές οι στήλες χρησιμοποιούνται στους όρους WHERE των εντολών UPDATE και DELETE και εξαιρούνται από τους όρους SET των UPDATE.

Αν δεν υπάρχει βασικό στιγμιότυπο, χρησιμοποιείται ως εφεδρική επιλογή η πρώτη στήλη του πίνακα τιμών. Αν ο πίνακας τιμών είναι κενός, θεωρείται το `id`.

## Μεταβίβαση οδηγού

Για να ελέγξετε την παράθεση αναγνωριστικών, μεταβιβάστε ένα στιγμιότυπο `Driver`:

```php
use Merql\Driver\SqliteDriver;

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