
# SQL tükör

Az `SqlMirror` a storh gyűjteményeket az SQLite vagy az MySQL tábláiba tölti, így egyesítéseket, táblák közötti rendezést, részszöveges keresést és jelentéseket futtathat az SQL rendszerben, miközben a fájlok maradnak a hiteles adatforrások. Tekintsen rá úgy, mint egy másodlagos indexre, amely történetesen adatbázis: a tükör származtatott, eldobható, és bármikor újraépíthető. Az `sqlite` vagy `mysql` illesztőprogrammal használt `ext-pdo` csak akkor szükséges, ha használja; az alapcsomagnak nincs függősége.

## Beállítás

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

Mindkét motor tükrözhető: a `DocStore` gyűjtemények és az `SegmentedLog` adatfolyamok (csak az aktív rekordok kerülnek át). A `Schema` megadása nem kötelező, és független az adattártól: a deklarált `string`/`int`/`float`/`bool` mezők típusos, indexelt oszlopokká válnak; minden más továbbra is lekérdezhető a `data` JSON oszlopban. A `mixed` mezők nem jelölhetők indexelésre.

## Kapcsolatok

A konstruktor `PDO` kezelőt (automatikusan felismeri az `sqlite` vagy `mysql` illesztőprogramot), `mysqli` kezelőt vagy bármely `SqlMirrorConnection` implementációt fogad el:

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

A mysqli útvonal mindkét mysqli hibajelentési móddal működik, így egy gazdaalkalmazás meglévő kezelője (például az WordPress `$wpdb->dbh` kezelője) változtatás nélkül újra felhasználható. A viselkedés azonos az illesztőprogramok között; a sikertelen feltöltések mindegyiknél visszagörgetik a gyűjtemény tranzakcióját.

Minden tábla megkapja az `id` mezőt (elsődleges kulcs), az `hash` mezőt (ez vezérli a növekményes feltöltéseket), a típusos oszlopokat, valamint az `data` mezőt (a teljes rekord JSON formában).

## Szinkronizálás

```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
```

Az `push()` összeveti a rekordkivonatokat a tükörrel, és minden gyűjteményt egyetlen tranzakcióban ír ki, ezért egy összeomlott vagy sikertelen feltöltés sem hagy félkész köteget maga után. A változatlan rekordok egyetlen `SELECT` műveletet és egy rekordbejárást igényelnek. Egy rendszeres `push()` (például egy cron ütemezett futtatás) fenntartja a tükör szinkronját; hívja meg az `flush()` függvényt az adott kérésben írt azonosítókkal, ha az olvasóknak azonnal látniuk kell a tükörben a saját írásaikat.

## Lekérdezés

A tükröt egyszerű SQL használatával kérdezze le. Az `table()` adja vissza a tükrözött tábla nevét:

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

## Visszatöltés fájlokba

Az `pull()` beolvassa a tükör sorait, majd azonosító szerinti sorrendben beírja őket a regisztrált adattárba. Két munkafolyamatot támogat:

- Visszaállítás: a fájlok elvesztek, de a tükör megmaradt. Regisztráljon egy új adattárat ugyanazzal a gyűjteménynévvel, majd végezze el a visszatöltést.
- Feltöltés: külső eszközök által a tükörtáblába betöltött sorokból storh rekordok lesznek. A soroknak érvényes UUIDv7 azonosítókat és egy objektumot kell tartalmazniuk az `data` oszlopban; a típusos oszlopokat a visszatöltés figyelmen kívül hagyja, mert a teljes rekordot az `data` tartalmazza.

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

A visszatöltött írások a szokásos adattárt használják, így érvényesül a sémaellenőrzés és az egyedi indexelés, minden rekord pedig tartósan kiíródik. Az `pull()` soha nem töröl helyi rekordokat, és kihagyja azokat, amelyek adatai már megegyeznek a tükör soraival. A kézzel beszúrt sorok feltöltése után futtassa le egyszer az `push()` műveletet: ez kanonikus formában írja újra a sorokat, hogy a kivonataik egyezzenek.

## Szerződés és korlátok

- A fájlok a hiteles adatforrások. A tükör csak olvasható kimenet; a kézzel beleírt adatokat a következő `push()` felülírja vagy törli.
- A tükör egyedi oszlopai védelmi hálót jelentenek, nem íráskori ellenőrzést: a fájlok által megengedett ismétlődés sikertelen (és teljesen visszagörgetett) feltöltést eredményez. Az íráskori ellenőrzéshez használjon storh egyedi indexeket.
- A tükör a fájlokhoz képest akár egy feltöltési időközzel is lemaradhat, kivéve azokat az azonosítókat, amelyeket kifejezetten megad az `flush()` függvénynek.
- A tükrözés önmagában nem ad tranzakciókat az storh rendszerhez. A több rekordot érintő atomi írások továbbra is egy valódi adatbázis feladatai, amely a rendszer hiteles adatforrásaként szolgál.
- A sémamódosítások a tükör eldobásával és újratelepítésével alkalmazhatók: `uninstall()`, `install()`, `push()`.
