SQL Mirror
SqlMirror przesyła kolekcje storh do tabel SQLite lub MySQL, aby umożliwić wykonywanie złączeń, sortowanie między tabelami, wyszukiwanie fragmentów tekstu i tworzenie raportów w SQL, przy zachowaniu kanonicznej postaci plików. To dodatkowy indeks, który jest jednocześnie bazą danych: mirror jest pochodny, można go usunąć i w każdej chwili odbudować.
ext-pdo sterownik sqlite lub mysql jest wymagany tylko wtedy, gdy z niego korzystasz; pakiet podstawowy nie ma zależności.
Konfiguracja
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();
Oba silniki mogą być synchronizowane z mirrorem: kolekcje DocStore i strumienie SegmentedLog (przesyłane są tylko aktywne rekordy). Przekazanie Schema jest opcjonalne i niezależne od magazynu: zadeklarowane pola string/int/float/bool stają się typowanymi, indeksowanymi kolumnami; pozostałe dane można odpytywać w kolumnie data JSON. Pola typu mixed nie mogą być oznaczone do indeksowania.
Połączenia
Konstruktor przyjmuje uchwyt PDO (automatycznie wykrywany sterownik sqlite lub mysql), uchwyt mysqli albo dowolną implementację 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_');
Ścieżka mysqli działa w obu trybach raportowania błędów mysqli, więc można bez zmian użyć istniejącego uchwytu z aplikacji hosta (na przykład WordPress — $wpdb->dbh). Zachowanie jest identyczne dla wszystkich sterowników; błędy przesyłania wycofują transakcję kolekcji w każdym z nich.
Każda tabela zawiera id (klucz główny), hash (obsługuje przyrostowe przesyłanie), typowane kolumny i data (pełny rekord JSON).
Synchronizacja
$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() porównuje skróty rekordów z mirrorem i zapisuje każdą kolekcję w ramach jednej transakcji, więc awaria lub nieudane przesyłanie nigdy nie pozostawia częściowo zastosowanej partii. Niezmienione rekordy wymagają jednego SELECT i przeglądu rekordów. Okresowe push() (na przykład przy cron) pozwala utrzymać aktualny mirror; wywołaj flush() z identyfikatorami zapisanymi podczas żądania, jeśli czytelnicy mają widzieć swoje zapisy w mirrorze.
Wykonywanie zapytań
Wykonuj zapytania do mirrora za pomocą zwykłego SQL. table() zwraca nazwę tabeli w mirrorze:
$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'
);
Przywracanie danych do plików
pull() odczytuje wiersze mirrora i zapisuje je w zarejestrowanym magazynie, w kolejności identyfikatorów. Obsługuje dwa przypadki:
- Przywracanie: pliki zniknęły, ale mirror przetrwał. Zarejestruj nowy magazyn pod tą samą nazwą kolekcji i pobierz dane.
- Inicjowanie danych: wiersze załadowane do tabeli mirrora przez zewnętrzne narzędzia stają się rekordami storh. Wiersze muszą zawierać prawidłowe identyfikatory UUIDv7 i obiekt w kolumnie
data; kolumny typowane są ignorowane przy pobieraniu, ponieważdatazawiera cały rekord.
$restored = new Storh\DocStore($root, 'pages');
$mirror = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull(); // ['written' => n, 'unchanged' => m]
Zapisane dane przechodzą przez standardowe mechanizmy magazynu, więc obowiązują walidacja schematu i unikalne indeksy, a każdy rekord jest trwale zapisywany. pull() nigdy nie usuwa lokalnych rekordów, a rekordy, których dane już odpowiadają wierszowi mirrora, są pomijane. Po ręcznym dodaniu wierszy uruchom raz push(): przepisze je do postaci kanonicznej, dzięki czemu ich skróty będą zgodne.
Zasady i ograniczenia
- Pliki są kanoniczne. Mirror zawiera dane tylko do odczytu; wszystko, co zostanie w nim ręcznie zapisane, zostanie nadpisane lub usunięte przy następnym
push(). - Unikalne kolumny w mirrorze pełnią funkcję zabezpieczenia, a nie ograniczenia podczas zapisu: duplikat, który był dozwolony w plikach, spowoduje nieudane przesyłanie i pełne wycofanie zmian. Do wymuszania unikalności podczas zapisu używaj unikalnych indeksów storh.
- Mirror może być opóźniony względem plików o maksymalnie jeden okres między przesyłaniami, z wyjątkiem identyfikatorów, które jawnie
flush(). - Synchronizacja z mirrorem nie zapewnia transakcji w samym storh. Atomowe zapisy wielu rekordów nadal wymagają prawdziwej bazy danych jako głównego źródła danych.
- Zmiany schematu są stosowane przez usunięcie i ponowną instalację mirrora:
uninstall(),install(),push().