Treci la conținut

SQL Mirror

SqlMirror transferă colecțiile storh în tabele SQLite sau MySQL, pentru a putea executa join-uri, ordonări între tabele, căutări de subșiruri și raportări în SQL, păstrând în același timp fișierele canonice. Gândiți-vă la aceasta ca la un index secundar care se întâmplă să fie o bază de date: oglinda este derivată, dispensabilă și poate fi reconstruită oricând. ext-pdo cu driverul sqlite sau mysql este necesar doar când folosiți oglinda; pachetul de bază nu are dependențe.

Configurare

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

Ambele motoare pot fi oglindite: colecțiile DocStore și fluxurile SegmentedLog (sunt transferate doar înregistrările active). Transmiterea unui Schema este opțională și independentă de stocare: câmpurile string/int/float/bool declarate devin coloane tipizate și indexate; toate celelalte rămân interogabile în coloana data JSON. Câmpurile mixed nu pot fi marcate pentru indexare.

Conexiuni

Constructorul acceptă un handle PDO (driver sqlite sau mysql, detectat automat), un handle mysqli sau orice implementare SqlMirrorConnection:

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

Calea mysqli funcționează cu oricare dintre modurile de raportare a erorilor mysqli, astfel că un handle existent dintr-o aplicație gazdă (de exemplu, cel al WordPress pentru $wpdb->dbh) poate fi reutilizat ca atare. Comportamentul este identic pentru toate driverele; eșecurile la transfer anulează tranzacția colecției în toate cazurile.

Fiecare tabel primește id (cheie primară), hash (pentru transferurile incrementale), coloanele tipizate și data (înregistrarea completă în JSON).

Sincronizare

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() compară hash-urile înregistrărilor cu oglinda și scrie fiecare colecție într-o singură tranzacție, astfel că un transfer întrerupt sau eșuat nu lasă niciodată un lot aplicat parțial. Înregistrările neschimbate necesită un singur SELECT plus parcurgerea înregistrărilor. Un push() periodic (de exemplu, la un eveniment cron) menține oglinda sincronizată; apelați flush() cu id-urile scrise în timpul unei cereri atunci când cititorii au nevoie să vadă în oglindă propriile scrieri.

Interogare

Interogați oglinda folosind instrucțiuni simple SQL. table() returnează numele tabelului oglindit:

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

Transferul înapoi în fișiere

pull() citește rândurile din oglindă și le scrie în stocarea înregistrată, în ordinea id-urilor. Acoperă două scenarii:

  • Restaurare: fișierele au dispărut, dar oglinda este încă disponibilă. Înregistrați o stocare nouă cu același nume de colecție și efectuați transferul.
  • Inițializare: rândurile încărcate în tabelul oglinzii cu instrumente externe devin înregistrări storh. Rândurile trebuie să aibă id-uri UUIDv7 valide și un obiect în coloana data; coloanele tipizate sunt ignorate la transfer, deoarece data conține înregistrarea completă.
PHP
$restored = new Storh\DocStore($root, 'pages');
$mirror   = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull();          // ['written' => n, 'unchanged' => m]

Scrierile transferate trec prin mecanismele normale ale stocării, astfel că se aplică validarea schemei și indexurile unice, iar fiecare înregistrare este scrisă durabil. pull() nu șterge niciodată înregistrările locale, iar înregistrările ale căror date corespund deja rândului din oglindă sunt omise. După inițializarea cu rânduri inserate manual, rulați o dată push(): acesta rescrie rândurile în formă canonică, astfel încât hash-urile lor să se reconcilieze.

Contract și limite

  • Fișierele sunt canonice. Oglinda este un rezultat doar pentru citire; orice date scrise manual în ea sunt suprascrise sau șterse la următorul push().
  • Coloanele unice din oglindă sunt o măsură de siguranță, nu o impunere la momentul scrierii: o dublură acceptată de fișiere va provoca eșecul transferului (și anularea completă a acestuia). Folosiți indexuri unice storh pentru impunerea unicității la scriere.
  • Oglinda rămâne în urma fișierelor cu până la un interval de transfer, exceptând id-urile pe care le sincronizați explicit prin flush().
  • Oglindirea nu adaugă tranzacții în storh. Scrierile atomice pe mai multe înregistrări rămân responsabilitatea unei baze de date reale, folosită ca sistem de referință.
  • Modificările schemei se aplică prin ștergerea și reinstalarea oglinzii: uninstall(), install(), push().