Aller au contenu

Miroir SQL

SqlMirror envoie les collections storh dans des tables SQLite ou MySQL afin que vous puissiez effectuer des jointures, trier entre plusieurs tables, rechercher des sous-chaînes et produire des rapports dans SQL, tandis que les fichiers restent la source canonique. Voyez cela comme un index secondaire qui se trouve être une base de données : le miroir est dérivé, jetable et peut être reconstruit à tout moment. ext-pdo avec le pilote sqlite ou mysql n’est nécessaire que si vous l’utilisez ; le paquet principal reste sans dépendance.

Configuration

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

Les deux moteurs peuvent être mis en miroir : les collections DocStore et les flux SegmentedLog (seuls les enregistrements actifs sont envoyés). Fournir un Schema est facultatif et indépendant du magasin : les champs string/int/float/bool déclarés deviennent des colonnes typées et indexées ; tout le reste reste interrogeable dans la colonne data JSON. Les champs mixed ne peuvent pas être marqués pour l’indexation.

Connexions

Le constructeur accepte un handle PDO (pilote sqlite ou mysql, détecté automatiquement), un handle mysqli ou toute implémentation de 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_');

La méthode mysqli fonctionne avec les deux modes de signalement des erreurs de mysqli ; un handle existant fourni par une application hôte (par exemple le $wpdb->dbh de WordPress) peut donc être réutilisé tel quel. Le comportement est identique avec tous les pilotes ; les échecs d’envoi annulent la transaction de la collection dans tous les cas.

Chaque table reçoit id (clé primaire), hash (utilisé pour les envois incrémentiels), les colonnes typées et data (l’JSON complet de l’enregistrement).

Synchronisation

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() compare les empreintes des enregistrements à celles du miroir et écrit chaque collection dans une seule transaction. Ainsi, un envoi interrompu ou en échec ne laisse jamais un lot partiellement appliqué. Pour les enregistrements inchangés, il suffit d’un SELECT et d’un parcours des enregistrements. Un push() périodique (par exemple à chaque cycle de cron) permet au miroir de rester à jour ; appelez flush() avec les identifiants écrits pendant une requête lorsque les lecteurs doivent voir leurs propres écritures dans le miroir.

Requêtes

Interrogez le miroir avec du SQL classique. table() renvoie le nom de la table mise en miroir :

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

Récupération des fichiers

pull() lit les lignes du miroir et les écrit dans le magasin enregistré, par ordre d’identifiant. Cette méthode couvre deux cas :

  • Restauration : les fichiers ont disparu, mais le miroir existe encore. Enregistrez un nouveau magasin sous le même nom de collection, puis récupérez les données.
  • Initialisation : les lignes chargées dans la table miroir par des outils externes deviennent des enregistrements storh. Elles doivent contenir des identifiants UUIDv7 valides et un objet dans la colonne data ; les colonnes typées sont ignorées lors de la récupération, car data contient l’enregistrement complet.
PHP
$restored = new Storh\DocStore($root, 'pages');
$mirror   = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull();          // ['written' => n, 'unchanged' => m]

Les écritures récupérées passent par le mécanisme habituel du magasin : la validation du schéma et les index uniques s’appliquent, et chaque enregistrement est écrit de façon durable. pull() ne supprime jamais les enregistrements locaux et ignore ceux dont les données correspondent déjà à la ligne du miroir. Après avoir initialisé le magasin avec des lignes insérées manuellement, exécutez push() une fois : ces lignes seront réécrites au format canonique, afin que leurs empreintes concordent.

Fonctionnement et limites

  • Les fichiers sont la source canonique. Le miroir est une sortie en lecture seule ; toute donnée qui y est écrite manuellement sera remplacée ou supprimée lors du prochain push().
  • Les colonnes uniques du miroir servent de protection supplémentaire et ne sont pas contrôlées au moment de l’écriture : un doublon autorisé par les fichiers entraîne l’échec de l’envoi, qui est entièrement annulé. Utilisez des index uniques storh pour appliquer la règle à l’écriture.
  • Le miroir peut avoir jusqu’à un intervalle d’envoi de retard sur les fichiers, à l’exception des identifiants que vous flush() explicitement.
  • La mise en miroir n’ajoute pas de transactions à storh lui-même. Les écritures atomiques portant sur plusieurs enregistrements restent du ressort d’une véritable base de données faisant office de système de référence.
  • Les changements de schéma sont appliqués en supprimant puis en réinstallant le miroir : uninstall(), install(), push().