Przejdź do treści

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

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

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:

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

Ś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

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

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

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ż data zawiera cały rekord.
PHP
$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().