SQL Mirror
SqlMirror überträgt storh-Sammlungen in SQLite- oder MySQL-Tabellen, damit du Joins, tabellenübergreifende Sortierung, Teilstringsuche und Berichte in SQL ausführen kannst, während die Dateien maßgeblich bleiben. Betrachte es als sekundären Index in Form einer Datenbank: Der Mirror ist abgeleitet, entbehrlich und jederzeit neu erstellbar. ext-pdo mit dem Treiber sqlite oder mysql ist nur erforderlich, wenn du ihn verwendest; das Kernpaket bleibt frei von Abhängigkeiten.
Einrichtung
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();
Beide Engines können gespiegelt werden: Sammlungen von DocStore und Streams von SegmentedLog (übertragen werden nur aktive Datensätze). Die Übergabe eines Schema ist optional und unabhängig vom Store: Deklarierte Felder string/int/float/bool werden zu typisierten, indizierten Spalten; alles andere bleibt in der data-Spalte JSON abfragbar. Felder vom Typ mixed können nicht zur Indizierung markiert werden.
Verbindungen
Der Konstruktor akzeptiert ein PDO-Handle (Treiber sqlite oder mysql, automatisch erkannt), ein mysqli-Handle oder eine beliebige Implementierung von 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_');
Der mysqli-Pfad funktioniert mit beiden Modi für die Fehlerberichterstattung von mysqli. Daher kann ein vorhandenes Handle einer Host-Anwendung (zum Beispiel WordPresss $wpdb->dbh) unverändert wiederverwendet werden. Das Verhalten ist bei allen Treibern gleich; schlägt eine Übertragung fehl, wird die Transaktion der Sammlung bei allen zurückgerollt.
Jede Tabelle erhält id (Primärschlüssel), hash (steuert inkrementelle Übertragungen), die typisierten Spalten und data (den vollständigen Datensatz JSON).
Synchronisierung
$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() vergleicht Datensatz-Hashes mit dem Mirror und schreibt jede Sammlung in einer Transaktion. Ein abgebrochener oder fehlgeschlagener Übertragungsvorgang hinterlässt daher nie einen teilweise angewendeten Batch. Für unveränderte Datensätze genügt ein SELECT plus ein Durchlauf durch die Datensätze. Ein regelmäßiges push() (zum Beispiel bei einem cron-Takt) hält den Mirror synchron; rufe flush() mit den IDs auf, die du während einer Anfrage geschrieben hast, wenn Leser ihre eigenen Änderungen im Mirror sofort sehen müssen.
Abfragen
Frage den Mirror mit einfachem SQL ab. table() gibt den Namen der gespiegelten Tabelle zurück:
$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'
);
Zurück in Dateien übertragen
pull() liest die Zeilen des Mirrors und schreibt sie sortiert nach ID in den registrierten Store. Das deckt zwei Anwendungsfälle ab:
- Wiederherstellung: Die Dateien sind verloren, aber der Mirror ist noch vorhanden. Registriere einen leeren Store unter demselben Sammlungsnamen und übertrage die Daten zurück.
- Befüllen: Zeilen, die externe Tools in die Mirror-Tabelle laden, werden zu storh-Datensätzen. Die Zeilen müssen gültige UUIDv7-IDs und ein Objekt in der Spalte
dataenthalten; beim Zurückübertragen werden typisierte Spalten ignoriert, dadataden vollständigen Datensatz enthält.
$restored = new Storh\DocStore($root, 'pages');
$mirror = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull(); // ['written' => n, 'unchanged' => m]
Zurückübertragene Schreibvorgänge durchlaufen die normale Store-Verarbeitung. Daher gelten Schemaüberprüfung und eindeutige Indizes, und jeder Datensatz wird dauerhaft gespeichert. pull() löscht niemals lokale Datensätze und überspringt Datensätze, deren Daten bereits mit der Mirror-Zeile übereinstimmen. Führe nach dem Befüllen mit manuell eingefügten Zeilen einmal push() aus: Dadurch werden sie in kanonischer Form neu geschrieben und ihre Hashes stimmen wieder überein.
Vereinbarungen und Grenzen
- Dateien sind maßgeblich. Der Mirror ist eine schreibgeschützte Ausgabe; manuelle Änderungen daran werden beim nächsten
push()überschrieben oder gelöscht. - Eindeutige Spalten im Mirror dienen als zusätzliche Absicherung, erzwingen Eindeutigkeit aber nicht beim Schreiben: Ein Duplikat, das die Dateien zugelassen haben, führt zu einer fehlgeschlagenen und vollständig zurückgerollten Übertragung. Verwende eindeutige storh-Indizes, um Eindeutigkeit beim Schreiben durchzusetzen.
- Der Mirror kann den Dateien um bis zu ein Übertragungsintervall hinterherhinken, außer bei IDs, die du ausdrücklich mit
flush()synchronisierst. - Das Spiegeln fügt storh selbst keine Transaktionen hinzu. Atomare Schreibvorgänge über mehrere Datensätze hinweg bleiben Aufgabe einer echten Datenbank als maßgeblichem System.
- Schemaänderungen werden angewendet, indem der Mirror gelöscht und neu eingerichtet wird:
uninstall(),install(),push().