
# SQL spogulis

`SqlMirror` pārsūta storh kolekcijas uz SQLite vai MySQL tabulām, lai varētu veikt savienošanu, kārtošanu starp tabulām, apakšvirkņu meklēšanu un pārskatu veidošanu SQL, kamēr faili paliek kanoniski. Uztveriet to kā sekundāru indeksu, kas vienlaikus ir datubāze: spogulis ir atvasināts un izmetams, un to var jebkurā brīdī izveidot no jauna. `ext-pdo` draiveris `sqlite` vai `mysql` ir nepieciešams tikai tad, ja izmantojat spoguli; galvenajai pakotnei nav atkarību.

## Iestatīšana

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

Var atspoguļot abu dzinēju datus: `DocStore` kolekcijas un `SegmentedLog` straumes (tiek pārsūtīti tikai aktīvie ieraksti). `Schema` nodošana nav obligāta un nav atkarīga no krātuves: deklarētie `string`/`int`/`float`/`bool` lauki kļūst par tipizētām, indeksētām kolonnām; pārējie ir pieejami vaicājumiem kolonnā `data` JSON. Laukus `mixed` nevar atzīmēt indeksēšanai.

## Savienojumi

Konstruktors pieņem `PDO` savienojuma objektu (automātiski tiek noteikts `sqlite` vai `mysql` draiveris), `mysqli` savienojuma objektu vai jebkuru `SqlMirrorConnection` implementāciju:

```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 ceļš darbojas abos mysqli kļūdu ziņošanas režīmos, tāpēc var izmantot esošu resursdatora lietotnes savienojuma objektu (piemēram, WordPress `$wpdb->dbh`). Visu draiveru darbība ir identiska; pārsūtīšanas kļūmes atceļ kolekcijas transakciju.

Katrai tabulai ir `id` (primārā atslēga), `hash` (nodrošina pakāpenisku pārsūtīšanu), tipizētās kolonnas un `data` (pilns ieraksts JSON).

## Sinhronizēšana

```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()` salīdzina ierakstu jaucējvērtības ar spoguli un ieraksta katru kolekciju vienā transakcijā, tāpēc neveiksmīga vai avarējusi pārsūtīšana nekad neatstāj daļēji piemērotu paketi. Nemainīti ieraksti prasa vienu `SELECT` un ierakstu pārbaudi. Periodiska `push()` (piemēram, no cron periodiskā uzdevuma) uztur spoguļa sinhronizāciju; kad lasītājiem spogulī nepieciešamas tikko rakstītās izmaiņas, izsauciet `flush()`, norādot pieprasījuma laikā ierakstīto ID.

## Vaicājumu izpilde

Vaicājumus spogulim veiciet, izmantojot vienkāršu SQL. `table()` atgriež atspoguļotās tabulas nosaukumu:

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

## Datu pārnešana atpakaļ failos

`pull()` nolasa spoguļa rindas un ieraksta tās reģistrētajā krātuvē pēc ID secības. Šī funkcija atbalsta divus lietošanas veidus:

- Atjaunošana: faili ir pazuduši, bet spogulis joprojām ir pieejams. Reģistrējiet jaunu krātuvi ar to pašu kolekcijas nosaukumu un pārnesiet datus.
- Sākotnējo datu ielāde: ārējos rīkos spoguļa tabulā ielādētās rindas kļūst par storh ierakstiem. Rindām jāietver derīgi UUIDv7 ID un objekts kolonnā `data`; tipizētās kolonnas netiek izmantotas, jo `data` ir viss ieraksts.

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

Pārsūtītie ieraksti izmanto parasto krātuves mehānismu, tāpēc tiek piemērota shēmas validācija un unikālie indeksi, un katrs ieraksts tiek saglabāts noturīgi. `pull()` nekad nedzēš lokālos ierakstus, un ieraksti, kuru dati jau atbilst spoguļa rindai, tiek izlaisti. Pēc manuāli ievietotu rindu sākotnējās ielādes vienreiz palaidiet `push()`: tas pārraksta šīs rindas kanoniskā formā, lai to jaucējvērtības būtu saskaņotas.

## Darbības noteikumi un ierobežojumi

- Kanoniskie dati ir faili. Spogulis ir tikai lasāma izvade; manuāli veiktās izmaiņas nākamā `push()` laikā tiek pārrakstītas vai dzēstas.
- Unikālās kolonnas spogulī kalpo kā papildu aizsardzība, nevis unikalitātes nodrošināšana ierakstīšanas brīdī: dublikāts, ko faili pieļāva, izraisa neveiksmīgu (un pilnībā atceltu) pārsūtīšanu. Unikalitāti ierakstīšanas brīdī nodrošiniet ar storh unikālajiem indeksiem.
- Spogulis var atpalikt no failiem par vienu pārsūtīšanas intervālu, izņemot ID, ko skaidri iekļaujat `flush()`.
- Spoguļošana pati par sevi nepievieno transakcijas storh. Vairāku ierakstu atomiska ierakstīšana joprojām jāveic īstā datubāzē, kur glabājas galvenā datu kopija.
- Shēmas izmaiņas piemēro, dzēšot un no jauna instalējot spoguli: `uninstall()`, `install()`, `push()`.
