
# Espelhamento de SQL

`SqlMirror` envia coleções de storh para tabelas SQLite ou MySQL para que você possa fazer junções, ordenação entre tabelas, pesquisa de substrings e relatórios em SQL, enquanto os arquivos permanecem canônicos. Pense nisso como um índice secundário que, por acaso, é um banco de dados: o espelho é derivado, descartável e pode ser reconstruído a qualquer momento. `ext-pdo` com o driver `sqlite` ou `mysql` é necessário somente quando você o utiliza; o pacote principal não tem dependências.

## Configuração

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

É possível espelhar ambos os mecanismos: coleções `DocStore` e fluxos `SegmentedLog` (somente registros ativos são enviados). Informar um `Schema` é opcional e independente do armazenamento: os campos `string`/`int`/`float`/`bool` declarados tornam-se colunas tipadas e indexadas; todo o resto continua consultável na coluna `data` JSON. Campos `mixed` não podem ser marcados para indexação.

## Conexões

O construtor aceita um identificador de `PDO` (driver `sqlite` ou `mysql`, detectado automaticamente), um identificador de `mysqli` ou qualquer implementação 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_');
```

O caminho mysqli funciona com qualquer um dos modos de relatório de erros do mysqli, então um identificador existente de um aplicativo hospedeiro (por exemplo, o `$wpdb->dbh` de WordPress) pode ser reutilizado sem alterações. O comportamento é idêntico em todos os drivers; falhas no envio revertem a transação da coleção em todos eles.

Cada tabela recebe `id` (chave primária), `hash` (usado nos envios incrementais), as colunas tipadas e `data` (o JSON completo do registro).

## Sincronização

```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()` compara os hashes dos registros com o espelho e grava cada coleção em uma única transação, de modo que uma falha ou interrupção nunca deixe um lote aplicado pela metade. Registros inalterados custam um `SELECT` mais uma varredura dos registros. Um `push()` periódico (por exemplo, em um ciclo de cron) mantém o espelho sincronizado; chame `flush()` com os ids que você gravou durante uma solicitação quando os leitores precisarem ver as próprias gravações no espelho.

## Consultas

Consulte o espelho com SQL puro. `table()` retorna o nome da tabela espelhada:

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

## Recuperação dos arquivos

`pull()` lê as linhas do espelho e as grava no armazenamento registrado, em ordem de id. Ele cobre dois fluxos:

- Restauração: os arquivos desapareceram, mas o espelho sobreviveu. Registre um armazenamento novo com o mesmo nome de coleção e faça a recuperação.
- Inicialização: linhas carregadas na tabela espelhada por ferramentas externas tornam-se registros storh. As linhas devem conter ids UUIDv7 válidos e um objeto na coluna `data`; as colunas tipadas são ignoradas na recuperação porque `data` é o registro completo.

```php
$restored = new Storh\DocStore($root, 'pages');
$mirror   = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull();          // ['written' => n, 'unchanged' => m]
```

As gravações recuperadas passam pelo fluxo normal do armazenamento, portanto a validação de esquema e os índices exclusivos são aplicados, e cada registro é gravado de forma durável. `pull()` nunca exclui registros locais, e os registros cujos dados já correspondem à linha do espelho são ignorados. Após inicializar com linhas inseridas manualmente, execute `push()` uma vez: ele regrava essas linhas no formato canônico para que os hashes sejam reconciliados.

## Contrato e limites

- Os arquivos são canônicos. O espelho é uma saída somente de leitura; qualquer conteúdo gravado manualmente nele será substituído ou excluído no próximo `push()`.
- Colunas exclusivas no espelho servem como proteção adicional, não como validação no momento da gravação: uma duplicata permitida pelos arquivos resulta em um envio com falha (e reversão completa). Use índices exclusivos de storh para validar no momento da gravação.
- O espelho pode ficar até um intervalo de envio atrasado em relação aos arquivos, exceto para os ids que você `flush()` explicitamente.
- O espelhamento não adiciona transações ao próprio storh. Gravações atômicas de vários registros continuam sendo tarefa de um banco de dados real como sistema de registro.
- As alterações de esquema são aplicadas removendo e reinstalando o espelho: `uninstall()`, `install()`, `push()`.
