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.
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:
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.
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.
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.
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.
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:
- Se a tabela tiver uma chave primária, use as colunas dessa chave.
- Se a tabela tiver chaves únicas, use as colunas da primeira chave única.
- 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.
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:
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:
$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.
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:
$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:
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.
// 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:
$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.