SQL Mirror
SqlMirror invia le raccolte storh nelle tabelle SQLite o MySQL, così potete eseguire join, ordinamenti tra tabelle, ricerche di sottostringhe e report in SQL, mantenendo canonici i file. È come un indice secondario che è anche un database: il mirror è derivato, eliminabile e ricostruibile in qualsiasi momento. ext-pdo con il driver sqlite o mysql è necessario solo quando lo usate; il pacchetto principale non ha dipendenze.
Configurazione
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();
È possibile creare mirror per entrambi i motori: raccolte DocStore e flussi SegmentedLog (vengono inviati solo i record attivi). Passare un Schema è facoltativo e indipendente dallo store: i campi string/int/float/bool dichiarati diventano colonne tipizzate e indicizzate; tutto il resto rimane interrogabile nella colonna data JSON. I campi mixed non possono essere contrassegnati per l'indicizzazione.
Connessioni
Il costruttore accetta un handle PDO (driver sqlite o mysql, rilevato automaticamente), un handle mysqli o qualsiasi implementazione SqlMirrorConnection:
$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_');
Il percorso mysqli funziona con entrambe le modalità di segnalazione degli errori di mysqli, quindi è possibile riutilizzare così com'è un handle esistente dell'applicazione host (per esempio WordPress di $wpdb->dbh). Il comportamento è identico con tutti i driver; gli errori di push annullano la transazione della raccolta in tutti i casi.
Ogni tabella include id (chiave primaria), hash (usato per i push incrementali), le colonne tipizzate e data (l'JSON completo del record).
Sincronizzazione
$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() confronta gli hash dei record con il mirror e scrive ogni raccolta in un'unica transazione, così un push interrotto o non riuscito non lascia mai un batch applicato solo in parte. I record invariati richiedono una sola SELECT oltre alla scansione dei record. Un push() periodico (per esempio a ogni ciclo di cron) mantiene il mirror aggiornato; chiamate flush() con gli id scritti durante una richiesta quando chi legge deve vedere nel mirror le proprie scritture.
Query
Interrogate il mirror con SQL semplice. table() restituisce il nome della tabella sottoposta a mirror:
$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'
);
Ripristino nei file
pull() legge le righe del mirror e le scrive nello store registrato, in ordine di id. Copre due flussi:
- Ripristino: i file sono scomparsi ma il mirror è ancora disponibile. Registrate un nuovo store con lo stesso nome di raccolta e richiamate il pull.
- Inizializzazione: le righe caricate nella tabella mirror da strumenti esterni diventano record storh. Le righe devono contenere id UUIDv7 validi e un oggetto nella colonna
data; al pull le colonne tipizzate vengono ignorate perchédataè il record completo.
$restored = new Storh\DocStore($root, 'pages');
$mirror = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull(); // ['written' => n, 'unchanged' => m]
Le scritture recuperate passano attraverso i normali meccanismi dello store, quindi si applicano la convalida dello schema e gli indici univoci, e ogni record viene scritto in modo persistente. pull() non elimina mai i record locali e salta quelli i cui dati corrispondono già alla riga del mirror. Dopo aver inizializzato le righe inserite manualmente, eseguite push() una volta: le riscrive nel formato canonico affinché i rispettivi hash siano coerenti.
Contratto e limiti
- I file sono canonici. Il mirror è un output di sola lettura; tutto ciò che vi viene scritto manualmente viene sovrascritto o eliminato al successivo
push(). - Le colonne univoche nel mirror sono una protezione aggiuntiva, non un controllo in fase di scrittura: un duplicato consentito dai file genera un push non riuscito (e annullato completamente). Usate indici univoci storh per i controlli in fase di scrittura.
- Il mirror può essere in ritardo rispetto ai file fino a un intervallo di push, tranne per gli id che
flush()esplicitamente. - Il mirroring non aggiunge transazioni a storh. Le scritture atomiche su più record richiedono un vero database come sistema di registrazione.
- Le modifiche allo schema vengono applicate eliminando e reinstallando il mirror:
uninstall(),install(),push().