
# SQL veidrodis

`SqlMirror` perkelia storh kolekcijas į SQLite arba MySQL lenteles, kad galėtumėte atlikti sujungimus, rikiavimą tarp lentelių, paiešką pagal poeilutes ir kurti ataskaitas naudodami SQL, o failai išlieka kanoninio formato. Tai tarsi antrinis indeksas, kuris kartu yra ir duomenų bazė: veidrodis gaunamas iš pirminių duomenų, jį galima išmesti ir bet kada atkurti.
`ext-pdo` su `sqlite` arba `mysql` tvarkykle reikalingas tik naudojant veidrodį; pagrindinis paketas neturi priklausomybių.

## Nustatymas

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

Abu varikliai gali naudoti veidrodį: kolekcijos `DocStore` ir srautai `SegmentedLog` (perkeliami tik esami įrašai). `Schema` pateikti nebūtina ir tai nepriklauso nuo saugyklos: deklaruoti `string`/`int`/`float`/`bool` laukai tampa tipuotais, indeksuotais stulpeliais; visa kita galima užklausti per `data` JSON stulpelį. `mixed` laukų negalima pažymėti kaip indeksuojamų.

## Ryšiai

Konstruktoriui galima perduoti `PDO` objektą (tvarkyklė `sqlite` arba `mysql`, aptinkama automatiškai), `mysqli` objektą arba bet kurią `SqlMirrorConnection` realizaciją:

```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 būdas veikia esant bet kuriam mysqli klaidų pranešimo režimui, todėl galima pakartotinai naudoti esamą pagrindinės programos objektą, pavyzdžiui, WordPress `$wpdb->dbh`. Visos tvarkyklės veikia vienodai; nepavykus perkelti duomenų, visose grąžinama kolekcijos transakcija.

Kiekviena lentelė turi `id` (pirminį raktą), `hash` (naudojamas perkeliant tik pakeistus įrašus), tipuotus stulpelius ir `data` (visą įrašą JSON).

## Sinchronizavimas

```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()` palygina įrašų maišas su veidrodžiu ir kiekvieną kolekciją įrašo per vieną transakciją, todėl dėl strigimo ar nesėkmingo perkėlimo nepaliekama iš dalies pritaikyta įrašų partija. Nepasikeitusiems įrašams reikia tik vieno `SELECT` ir visų įrašų peržiūros. Periodiškai paleiskite `push()` (pavyzdžiui, iš cron užduoties), kad veidrodis būtų sinchronizuotas; kai skaitytojams reikia, kad jų pakeitimai būtų iškart matomi veidrodyje, perduokite `flush()` užklausoje įrašytų įrašų ID.

## Užklausos

Veidrodžio užklausoms naudokite paprastą SQL. `table()` grąžina veidrodinės lentelės pavadinimą:

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

## Duomenų grąžinimas į failus

`pull()` skaito veidrodžio eilutes ir įrašo jas į registruotą saugyklą pagal ID. Jis skirtas dviem atvejams:

- Atkūrimas: failai prarasti, bet veidrodis išliko. Užregistruokite naują saugyklą tuo pačiu kolekcijos pavadinimu ir grąžinkite duomenis.
- Pradinių duomenų įkėlimas: išoriniais įrankiais į veidrodžio lentelę įkeltos eilutės tampa storh įrašais. Eilutėse turi būti galiojantys UUIDv7 ID ir objektas stulpelyje `data`; tipuotų stulpelių reikšmės grąžinant ignoruojamos, nes `data` yra visas įrašas.

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

Grąžinami įrašai apdorojami įprastais saugyklos metodais, todėl taikomas schemos tikrinimas ir unikalūs indeksai, o kiekvienas įrašas išsaugomas patvariai. `pull()` niekada neištrina vietinių įrašų; įrašai, kurių duomenys jau sutampa su veidrodžio eilute, praleidžiami. Įkėlę eilutes rankiniu būdu, vieną kartą paleiskite `push()`: eilutės bus perrašytos kanoniniu formatu, kad jų maišos sutaptų.

## Sąlygos ir apribojimai

- Failai yra kanoninis šaltinis. Veidrodis yra tik skaitoma išvestis; viskas, kas į jį įrašoma rankiniu būdu,
  per kitą `push()` bus perrašyta arba ištrinta.
- Unikalūs veidrodžio stulpeliai yra atsarginė apsauga, o ne įrašymo metu taikomas apribojimas: dublikatas, kurį leido failai, pasireiškia kaip nepavykęs (ir visiškai atšauktas) siuntimas. Įrašymo metu taikykite storh unikalius indeksus.
- Veidrodis atsilieka nuo failų iki vieno siuntimo intervalo, išskyrus ID, kuriuos `flush()` aiškiai nurodote.
- Veidrodinis atvaizdavimas neprideda transakcijų pačiam storh. Kelių įrašų atominis įrašymas tebėra tikros duomenų bazės, kaip sistemos įrašų šaltinio, užduotis.
- Schemos pakeitimai pritaikomi ištrinant ir iš naujo įdiegiant veidrodį: `uninstall()`, `install()`, `push()`.
