
# SQL-peilaus

`SqlMirror` siirtää storh-kokoelmia kohteen SQLite tai MySQL tauluihin, jotta voit suorittaa yhdistämisiä, taulujen välisiä järjestyksiä, osamerkkijonohakuja ja raportointia kohteessa SQL tiedostojen pysyessä ensisijaisina. Ajattele sitä toissijaisena indeksinä, joka sattuu olemaan tietokanta: peili on johdettu, hävitettävissä ja rakennettavissa uudelleen milloin tahansa. `ext-pdo` ohjaimen `sqlite` tai `mysql` kanssa tarvitaan vain, kun käytät sitä; ydinpaketti pysyy riippuvuuksista vapaana.

## Määritys

```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();
```

Molemmat moottorit voidaan peilata: `DocStore`-kokoelmat ja `SegmentedLog`-virrat (vain aktiiviset tietueet siirretään). `Schema`-arvon välittäminen on valinnaista ja riippumatonta säilöstä: määritellyistä `string`/`int`/`float`/`bool`-kentistä tulee tyypitettyjä, indeksoituja sarakkeita; kaikki muu pysyy kyseltävissä sarakkeessa `data` JSON. `mixed`-kenttiä ei voi merkitä indeksoitaviksi.

## Yhteydet

Konstruktorille voi antaa `PDO`-kahvan (`sqlite`- tai `mysql`-ohjain, tunnistetaan automaattisesti), `mysqli`-kahvan tai minkä tahansa `SqlMirrorConnection`-toteutuksen:

```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-polku toimii kummassakin mysqli-virheraportointitilassa, joten isäntäsovelluksen olemassa olevaa kahvaa (esimerkiksi WordPress-sovelluksen `$wpdb->dbh`) voi käyttää sellaisenaan uudelleen. Toiminta on samanlaista kaikilla ohjaimilla; siirron epäonnistuminen peruuttaa kokoelman tapahtuman kaikissa niissä.

Jokaiseen tauluun tulee `id` (pääavain), `hash` (ohjaa inkrementaalisia siirtoja), tyypitetyt sarakkeet ja `data` (koko tietueen JSON).

## Synkronointi

```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()` vertaa tietueiden tiivisteitä peiliin ja kirjoittaa kunkin kokoelman yhdessä tapahtumassa, joten kaatunut tai epäonnistunut siirto ei koskaan jätä erää osittain sovelletuksi. Muuttumattomat tietueet maksavat yhden `SELECT`-operaation sekä tietueiden läpikäynnin. Säännöllinen `push()` (esimerkiksi cron-ajastuksen yhteydessä) pitää peilin ajan tasalla; kutsu `flush()`-toimintoa tunnisteilla, jotka kirjoitit pyynnön aikana, kun lukijoiden on nähtävä omat kirjoituksensa peilistä.

## Kyselyt

Kysele peiliä tavallisella SQL-kielellä. `table()` palauttaa peilatun taulun nimen:

```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'
);
```

## Tietojen palauttaminen tiedostoihin

`pull()` lukee peilirivit ja kirjoittaa ne rekisteröityyn säilöön tunnistejärjestyksessä. Se kattaa kaksi käyttötapaa:

- Palautus: tiedostot ovat kadonneet, mutta peili on jäljellä. Rekisteröi uusi säilö samalla kokoelman nimellä ja tuo tiedot takaisin.
- Alustus: ulkoisilla työkaluilla peilitauluun ladatuista riveistä tulee storh-tietueita. Rivien on sisällettävä kelvolliset UUIDv7-tunnisteet ja objekti sarakkeessa `data`; tyypitetyt sarakkeet ohitetaan tuonnissa, koska `data` sisältää koko tietueen.

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

Tuodut kirjoitukset kulkevat tavallisen säilötoiminnallisuuden kautta, joten skeeman validointi ja yksilölliset indeksit ovat voimassa ja jokainen tietue kirjoitetaan pysyvästi. `pull()` ei koskaan poista paikallisia tietueita, ja tietueet, joiden tiedot vastaavat jo peiliriviä, ohitetaan. Kun olet alustanut käsin lisätyillä riveillä, suorita `push()` kerran: se kirjoittaa rivit uudelleen kanoniseen muotoon, jolloin niiden tiivisteet täsmäävät.

## Sopimus ja rajoitukset

- Tiedostot ovat ensisijaisia. Peili on vain luku -tuloste; käsin sinne kirjoitettu ylikirjoitetaan tai poistetaan seuraavassa `push()`-ajossa.
- Peilin yksilölliset sarakkeet ovat varmistus, eivät kirjoitushetken valvontaa: tiedostojen sallima kaksoiskappale näkyy epäonnistuneena (ja kokonaan peruutettuna) siirtona. Käytä storh-yksilöllisiä indeksejä kirjoitushetken valvontaan.
- Peili voi olla tiedostoista jäljessä yhden siirtovälin verran, paitsi tunnisteiden osalta, jotka `flush()` erikseen.
- Peilaus ei lisää tapahtumia itse storh-järjestelmään. Usean tietueen atomiset kirjoitukset kuuluvat edelleen varsinaiselle tietokannalle, joka toimii tietojen ensisijaisena lähteenä.
- Skeeman muutokset otetaan käyttöön poistamalla peili ja asentamalla se uudelleen: `uninstall()`, `install()`, `push()`.
