
# Identidade da linha

A identidade da linha determina como merql associa linhas entre snapshots. Ao comparar base com ours, é preciso saber que a linha X em base é a mesma linha lógica que a linha Y em ours, mesmo que alguns valores das colunas tenham mudado. A regra de identidade define quais colunas formam a chave da linha.

```php
use Merql\Identity\PrimaryKeyIdentity;

$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"
```

## A interface RowIdentity

Todas as estratégias de identidade implementam dois métodos:

```php
interface RowIdentity
{
    public function key(array $row): string;
    public function columns(): array;
}
```

`key()` cria um identificador de string único a partir dos dados de uma linha. `columns()` retorna os nomes das colunas usados para identificar a linha.

## PrimaryKeyIdentity

A estratégia padrão. Associa linhas pelas colunas da chave primária.

```php
use Merql\Identity\PrimaryKeyIdentity;

// Single-column PK.
$identity = new PrimaryKeyIdentity(['id']);
$key = $identity->key(['id' => 42, 'title' => 'Hello']);
// "42"

// Composite PK.
$identity = new PrimaryKeyIdentity(['tenant_id', 'user_id']);
$key = $identity->key(['tenant_id' => 1, 'user_id' => 99, 'name' => 'Alice']);
// "1\x1F99" (unit separator between parts)
```

É isso que merql usa por padrão quando a tabela tem uma chave primária definida no esquema.

## NaturalKeyIdentity

Em tabelas sem chave primária com incremento automático, associa linhas pelas colunas de restrição única. É útil quando a PK é uma UUID ou quando a tabela usa uma restrição única composta como identidade lógica.

```php
use Merql\Identity\NaturalKeyIdentity;

$identity = new NaturalKeyIdentity(['email']);
$key = $identity->key(['id' => 1, 'email' => 'alice@example.com', 'name' => 'Alice']);
// "alice@example.com"

$identity = new NaturalKeyIdentity(['country', 'postal_code']);
$key = $identity->key(['country' => 'US', 'postal_code' => '90210', 'city' => 'Beverly Hills']);
// "US\x1F90210"
```

## ContentHashIdentity

Alternativa para tabelas sem chave primária nem restrições únicas. Calcula um hash SHA-256 de todos os valores das colunas. Duas linhas são iguais se todas as colunas coincidirem.

```php
use Merql\Identity\ContentHashIdentity;

$identity = new ContentHashIdentity(['name', 'value']);
$key = $identity->key(['name' => 'color', 'value' => 'blue']);
// "a3f2..." (SHA-256 hash)
```

A identidade por hash de conteúdo tem uma limitação: se o conteúdo de uma linha mudar, seu hash também muda. Assim, a linha aparece como uma exclusão do hash antigo e uma inserção do novo, em vez de uma atualização. Essa é uma consequência inerente à ausência de colunas de identidade estáveis.

## IdentityRule

`IdentityRule` é a configuração de identidade serializável usada pela captura de snapshots e pelos planos de mesclagem.

```php
use Merql\Identity\IdentityRule;

$rule = IdentityRule::natural(['option_name']);
$key = $rule->key(['option_name' => 'siteurl']);
// "siteurl"
```

Tipos de regra:

| Tipo | Significado |
|---|---|
| `primary` | Colunas da chave primária do banco de dados |
| `natural` | Colunas comerciais únicas e estáveis |
| `composite` | Colunas de identidade lógica composta |
| `content` | Alternativa de hash de conteúdo |

`IdentityRule::forSchema($schema)` resolve a regra padrão:

1. Se a tabela tiver uma chave primária, use as colunas dessa chave.
2. Se a tabela tiver chaves únicas, use as colunas da primeira chave única.
3. Se não tiver nenhuma das duas, use todas as colunas (alternativa de hash de conteúdo).

## IdentityRuleSet

Use `IdentityRuleSet` quando uma aplicação hospedeira conhecer uma regra de identidade melhor que o padrão do esquema.

```php
use Merql\Identity\IdentityRule;
use Merql\Identity\IdentityRuleSet;
use Merql\Snapshot\Snapshotter;

$rules = new IdentityRuleSet([
    'wp_options' => IdentityRule::natural(['option_name']),
    'wp_term_relationships' => IdentityRule::composite(['object_id', 'term_taxonomy_id']),
]);

$snapshotter = new Snapshotter($connection, identityRules: $rules);
$snapshot = $snapshotter->capture('live', ['wp_options', 'wp_term_relationships']);
```

`Snapshotter` resolve um `IdentityRule` para cada tabela capturada. As colunas escolhidas são armazenadas em `TableSnapshot::identityColumns` e usadas em todas as operações subsequentes de diff, mesclagem, plano, SQL e reversão.

`Merql::init()` pode receber o mesmo conjunto de regras ao usar a fachada estática:

```php
use Merql\Merql;

Merql::init($connection, identityRules: $rules);
Merql::snapshot('live', ['wp_options']);
```

Os conjuntos de regras têm serialização de array estável:

```php
$payload = $rules->toArray();
$rules = IdentityRuleSet::fromArray($payload);
```

## Identidade ambígua

Se duas linhas capturadas produzirem a mesma chave de identidade, a captura falhará em vez de sobrescrever uma linha com outra.

```php
use Merql\Exceptions\SnapshotException;

try {
    $snapshotter->capture('live', ['wp_postmeta']);
} catch (SnapshotException $e) {
    // The configured identity rule matched multiple rows.
}
```

Aplicações hospedeiras podem fazer uma verificação prévia das linhas brutas com `IdentityRuleSet::conflictsFor()` para detectar a ambiguidade antes da captura:

```php
$conflicts = $rules->conflictsFor(
    'wp_postmeta',
    IdentityRule::natural(['post_id', 'meta_key']),
    $rows,
);
```

Para tabelas meta de WordPress, `post_id + meta_key` costuma ser útil para agrupamento e exibição, mas não é seguro como identidade de linha, pois chaves meta duplicadas são válidas. Use a chave primária da tabela como identidade da linha, a menos que a aplicação hospedeira possa comprovar que a chave natural é única.

## Codificação da chave da linha

As chaves de linha são strings codificadas que combinam vários valores de coluna usando um separador de unidade (`\x1F`). Valores que contenham o separador ou o sinal de porcentagem são escapados para evitar colisões:

```php
use Merql\Snapshot\Snapshotter;

// Encode.
$key = Snapshotter::buildRowKey(
    ['tenant_id' => '1', 'user_id' => '99'],
    ['tenant_id', 'user_id'],
);
// "1\x1F99"

// Decode.
$parts = Snapshotter::decodeRowKey($key);
// ['1', '99']
```

Regras de escape:

| Original | Escapado |
|---|---|
| `%` | `%25` |
| `\x1F` (separador de unidade) | `%1F` |

Isso garante que um valor contendo `\x1F` não seja dividido em várias partes da chave. A codificação é determinística e reversível.

## Divergência de incremento automático

Quando os dois lados inserem linhas novas, os IDs de incremento automático serão diferentes entre as ramificações. A linha #100 em ours pode ser um registro completamente diferente da linha #100 em theirs.

merql lida com isso associando linhas pela chave primária. Se os dois lados inserirem uma linha com o mesmo ID de incremento automático, ela será sinalizada como um conflito `insert_insert`. Para evitar isso, use chaves naturais (colunas únicas e estáveis entre ramificações) em tabelas nas quais se esperam inserções simultâneas.

```php
// A natural key on (slug) avoids auto-increment collisions.
$identity = new NaturalKeyIdentity(['slug']);
```

## Identidade em snapshots

As colunas de identidade escolhidas durante a captura são armazenadas com cada `TableSnapshot`:

```php
$tableSnapshot = $snapshot->getTable('posts');
$tableSnapshot->identityColumns;
// ['id']
```

Essas colunas são usadas por `SqlGenerator` para criar cláusulas WHERE para instruções UPDATE e DELETE e pelos planos de mesclagem para expor valores de identidade decodificados para análise, seleção, aplicação protegida e reversão.
