
# SQL peegel

`SqlMirror` saadab storh kogud SQLite või MySQL tabelitesse, et saaksid SQL-is teha liitmisi, järjestada andmeid tabeliteüleselt, otsida alamstringe ja koostada aruandeid, samal ajal kui failid jäävad kanooniliseks allikaks. Mõtle sellest kui teisesest indeksist, mis on ühtlasi andmebaas: peegel on tuletatud, ära visatav ja igal ajal taastatav. `ext-pdo` koos draiveriga `sqlite` või `mysql` on vajalik ainult siis, kui seda kasutad; põhipaketil sõltuvusi pole.

## Seadistamine

```php
use Storh\DocStore;
use Storh\Schema;
use Storh\SqlMirror;

$schema = Schema::collection('pages')
    ->string('slug')->unique()
    ->string('kind')->index()
    ->int('publishedAt')->range();

$pages  = new DocStore($root, 'pages', schema: $schema);
$events = new Storh\SegmentedLog($root, 'events');

$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'), 'app_');
$mirror->collection($pages, 'pages', $schema);
$mirror->collection($events, 'events');

$mirror->install();
$mirror->push();
```

Mõlemat mootorit saab peegeldada: `DocStore` kogusid ja `SegmentedLog` vooge (saadetakse ainult aktiivsed kirjed). `Schema` edastamine on valikuline ega sõltu andmehoidlast: deklareeritud väljad `string`/`int`/`float`/`bool` muutuvad tüübistatud, indekseeritud veergudeks; kõik muu jääb päringu jaoks veergu `data` JSON. Välju `mixed` ei saa märgistada indekseeritavaks.

## Ühendused

Konstruktor võtab vastu `PDO` käepideme (`sqlite` või `mysql` draiver, tuvastatakse automaatselt), `mysqli` käepideme või mis tahes `SqlMirrorConnection` teostuse:

```php
$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'));
$mirror = new SqlMirror(new PDO('mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', $user, $password));
$mirror = new SqlMirror(new mysqli('127.0.0.1', $user, $password, 'app'), 'wp_storh_');
```

mysqli tee töötab mõlema mysqli veateavituse režiimiga, nii et hostrakenduse olemasolevat käepidet (näiteks WordPress `$wpdb->dbh` oma) saab muutmata kujul uuesti kasutada. Käitumine on kõigi draiverite puhul sama; saatmise tõrgete korral pööratakse kõigi puhul kogu kollektsiooni tehing tagasi.

Igale tabelile lisatakse `id` (primaarvõti), `hash` (juhib järkjärgulist saatmist), tüübistatud veerud ja `data` (kirje täielik JSON).

## Sünkroonimine

```php
$mirror->push();                    // reconcile: insert, update, delete as needed
$mirror->push('pages');             // reconcile a single collection
$mirror->flush('pages', $ids);      // push or remove just these ids
$mirror->pull();                    // write mirror rows back into the store
$mirror->verify();                  // report missing, stale, and orphaned rows
$mirror->rebuild();                 // clear all rows and push from scratch
$mirror->uninstall();               // drop the mirror tables
```

`push()` võrdleb kirjete räside erinevusi peegliga ja kirjutab iga kogu ühe tehinguna, nii et kokku jooksnud või nurjunud saatmine ei jäta kunagi poolikut paketti. Muutmata kirjete töötlemiseks piisab ühest `SELECT`-ist ja kirjete läbikäimisest. Korrapärane `push()` (näiteks cron käivituse ajal) hoiab peegli ajakohasena; kui lugejad peavad päringu käigus kirjutatud kirjete muudatusi kohe peeglis nägema, kutsu `flush()` ja anna kaasa nende kirjete ID-d.

## Päringud

Päringu tegemiseks kasuta peeglis tavalist SQL-i. `table()` tagastab peegeldatud tabeli nime:

```php
$pdo->query(
    'SELECT p.slug, COUNT(*) AS sales
     FROM ' . $mirror->table('purchases') . ' o
     INNER JOIN ' . $mirror->table('products') . ' p ON p.id = o.product_id
     GROUP BY p.slug
     ORDER BY sales DESC'
);
```

## Andmete failidesse tagasitoomine

`pull()` loeb peegli ridu ja kirjutab need registreeritud andmehoidlasse ID-de järjekorras. See katab kaks kasutusjuhtu:

- Taastamine: failid on kadunud, kuid peegel on alles. Registreeri sama kogu nime all uus andmehoidla ja too andmed tagasi.
- Algandmetega täitmine: väliste tööriistade abil peeglitabelisse laaditud ridadest saavad storh kirjed. Ridadel peavad olema kehtivad UUIDv7 ID-d ja veerus `data` peab olema objekt; tüübistatud veerge tagasitoomisel eiratakse, sest `data` sisaldab kogu kirjet.

```php
$restored = new Storh\DocStore($root, 'pages');
$mirror   = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull();          // ['written' => n, 'unchanged' => m]
```

Tagasitoomisel kasutatakse andmehoidla tavapärast töövoogu, seega rakenduvad skeemi valideerimine ja unikaalsed indeksid ning iga kirje kirjutatakse püsivalt kettale. `pull()` ei kustuta kohalikke kirjeid ja kirjed, mille andmed kattuvad peeglireaga, jäetakse vahele. Pärast käsitsi lisatud ridade algandmetena laadimist käivita `push()` üks kord: see kirjutab need read kanoonilisel kujul ümber, et nende räsid saaksid sünkroonitud.

## Tingimused ja piirangud

- Kanoonilised andmed asuvad failides. Peegel on kirjutuskaitstud väljund; käsitsi sinna kirjutatu kirjutatakse järgmise `push()` ajal üle või kustutatakse.
- Peegli unikaalsed veerud on kaitsemeede, mitte kirjutusaegne kontroll: kui failid lubasid duplikaadi, nurjub selle saatmine ja kogu tehing pööratakse tagasi. Kirjutamise ajal kontrollimiseks kasuta storh unikaalseid indekseid.
- Peegel võib failidest maha jääda kuni ühe saatmisintervalli võrra, välja arvatud nende ID-de puhul, mille saadad selgesõnaliselt käsuga `flush()`.
- Peegeldamine ei lisa tehinguid otse storh-le. Mitme kirje aatomkirjutused tuleb endiselt teha päris andmebaasis, mis on süsteemi tõeallikas.
- Skeemi muudatused rakendatakse peegli kustutamise ja uuesti paigaldamisega: `uninstall()`, `install()`, `push()`.
