
# Radidentitet

Radidentitet avgör hur merql matchar rader i olika ögonblicksbilder. När base jämförs med ours måste systemet veta att rad X i base är samma logiska rad som rad Y i ours, även om vissa kolumnvärden har ändrats. Identitetsregeln styr vilka kolumner som bildar radnyckeln.

```php
use Merql\Identity\PrimaryKeyIdentity;

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

## Gränssnittet RowIdentity

Alla identitetsstrategier implementerar två metoder:

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

`key()` skapar en unik strängidentifierare utifrån en rads data. `columns()` returnerar kolumnnamnen som används för identitet.

## PrimaryKeyIdentity

Standardstrategin. Matchar rader med deras primärnyckelkolumner.

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

Det här är standardmetoden som merql använder när tabellens schema definierar en primärnyckel.

## NaturalKeyIdentity

För tabeller utan autoinkrementerande primärnyckel matchas rader med kolumnerna i den unika begränsningen. Användbart när PK är en UUID eller när tabellen använder en sammansatt unik begränsning som logisk identitet.

```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

Reservlösning för tabeller utan primärnyckel eller unika begränsningar. Beräknar en SHA-256-hash av alla kolumnvärden. Två rader är samma om varje kolumn matchar.

```php
use Merql\Identity\ContentHashIdentity;

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

Innehållshash som identitet har en begränsning: om en rads innehåll ändras ändras även dess hash. Raden visas då som en borttagning av den gamla hashen och en infogning av den nya, i stället för som en uppdatering. Det är en ofrånkomlig avvägning när det saknas stabila identitetskolumner.

## IdentityRule

`IdentityRule` är den serialiserbara identitetskonfiguration som används när ögonblicksbilder tas och sammanslagningsplaner skapas.

```php
use Merql\Identity\IdentityRule;

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

Regeltyper:

| Typ | Betydelse |
|---|---|
| `primary` | Primärnyckelkolumner i databasen |
| `natural` | Stabilt unika verksamhetskolumner |
| `composite` | Kolumner för sammansatt logisk identitet |
| `content` | Reservlösning med innehållshash |

`IdentityRule::forSchema($schema)` löser standardregeln:

1. Om tabellen har en primärnyckel används dess kolumner.
2. Om tabellen har unika nycklar används kolumnerna i den första unika nyckeln.
3. Om inget av detta finns används alla kolumner (reservlösning med innehållshash).

## IdentityRuleSet

Använd `IdentityRuleSet` när värdapplikationen känner till en bättre identitetsregel än den som schemat anger som standard.

```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` väljer en `IdentityRule` för varje tabell som tas med. De valda kolumnerna lagras i `TableSnapshot::identityColumns` och används sedan för alla diff-, merge-, plan-, SQL- och rollback-operationer.

`Merql::init()` kan ta emot samma regeluppsättning när den statiska fasaden används:

```php
use Merql\Merql;

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

Regeluppsättningar serialiseras stabilt som arrayer:

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

## Tvetydig identitet

Om två rader i ögonblicksbilden får samma identitetsnyckel misslyckas registreringen i stället för att den ena raden skriver över den andra.

```php
use Merql\Exceptions\SnapshotException;

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

Värdapplikationer kan kontrollera rådata med `IdentityRuleSet::conflictsFor()` i förväg om de vill rapportera tvetydigheter innan ögonblicksbilden tas:

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

För WordPress-metatabeller är `post_id + meta_key` oftast användbar för gruppering och visning, men inte säker som radidentitet eftersom duplicerade metanycklar är tillåtna. Använd tabellens primärnyckel som radidentitet, såvida inte värdapplikationen kan bevisa att den naturliga nyckeln är unik.

## Kodning av radnycklar

Radnycklar är kodade strängar där flera kolumnvärden kombineras med en enhetsavskiljare (`\x1F`). Värden som innehåller avskiljaren eller procenttecknet escape-kodas för att förhindra kollisioner:

```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']
```

Escape-regler:

| Ursprungligt | Escape-kodat |
|---|---|
| `%` | `%25` |
| `\x1F` (enhetsavskiljare) | `%1F` |

Detta säkerställer att ett värde som innehåller `\x1F` inte delas upp i flera nyckeldelar. Kodningen är deterministisk och reversibel.

## Avvikande autoinkrement

När båda sidor infogar nya rader får de olika autoinkrement-ID:n. Rad #100 i ours kan vara en helt annan post än rad #100 i theirs.

merql hanterar detta genom att matcha på primärnyckel. Om båda sidor har infogat en rad med samma autoinkrement-ID markeras det som en `insert_insert`-konflikt. Undvik detta genom att använda naturliga nycklar (unika kolumner som är stabila mellan grenar) för tabeller där samtidiga infogningar kan förekomma.

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

## Identitet i ögonblicksbilder

Identitetskolumnerna som valdes när ögonblicksbilden togs lagras med varje `TableSnapshot`:

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

`SqlGenerator` använder dessa kolumner för att skapa WHERE-satser för UPDATE- och DELETE-satser. Sammanslagningsplaner använder dem för att visa avkodade identitetsvärden för granskning, val, skyddad tillämpning och återställning.
