Hash estrutural
A Curadoria já documenta o que acontece do ponto de vista de quem
usa o ddf: um hash decide se a estrutura de uma tabela mudou desde a última execução, e
o override é preservado ou atualizado a partir disso. Esta página cobre o porquê da
implementação por trás desse comportamento, na função _calcular_hash_estrutural, dentro
da SobrescritaDeTabela, a Anti-Corruption Layer entre Extraction e Curation.
O que entra no hash
O hash é um SHA-256 sobre uma lista ordenada de partes, concatenadas com um separador antes de serem codificadas:
- Nome do escopo e nome da tabela.
- Para cada coluna, na ordem em que aparece em
tabela.colunas: nome, o tipo de dado serializado como JSON, se é chave primária, se é chave estrangeira, se aceita nulo, se é única, e as referências de chave estrangeira (também serializadas). - Cada restrição
UNIQUEcomposta da tabela. - Cada restrição de chave estrangeira composta da tabela.
Qualquer diferença em qualquer um desses campos, em qualquer coluna, produz um hash diferente do gravado no override.
Limitação conhecida: sensibilidade à ordem das colunas
A lista de colunas é percorrida na ordem em que tabela.colunas as apresenta, não ordenada
por nome nem tratada como um conjunto. Isso tem uma consequência observável onde reordenar
colunas na fonte, sem adicionar, remover ou alterar nenhuma, muda o hash, porque a posição
de cada coluna na lista afeta a posição das suas partes na string concatenada antes do
SHA-256.
Isso não é uma escolha de design registrada em lugar nenhum. A sensibilidade a reordenação está ausente desse histórico inteiro, porque é efeito colateral da implementação mais direta (percorrer a lista na ordem em que ela vem), não um trade-off avaliado e aceito.
O impacto real é retrabalho desnecessário, não perda de dado. A curadoria no skeleton
YAML é chaveada por nome de coluna (um dict, não uma lista), então uma reordenação
pura nunca descarta curadoria já feita. O que ela dispara é uma reavaliação
desnecessária do skeleton: o hash diverge, o arquivo é reescrito, e o Aviso genérico
("estrutura mudou, nomes preservados") é emitido mesmo sem nenhuma mudança semântica na
tabela, um alarme falso e um diff espúrio no skeleton versionado.
Por que hash sobre serialização, não comparação direta dos objetos Pydantic
O override em disco guarda só o hash (hash: str, um campo do YAML), não uma cópia
serializada da TabelaExtraida inteira da execução anterior. Comparar objetos Pydantic
diretamente exigiria persistir e recarregar a estrutura completa da execução anterior a
cada reexecução, um artefato maior e mais frágil a mudanças na própria definição dos
modelos (campo novo em ColunaExtraida, por exemplo, quebraria a comparação de um objeto
salvo por uma versão anterior do ddf). Um hash de string:
- É uma única linha no YAML, fácil de revisar em um diff de Git: a mudança de hash em si
já sinaliza "algo estrutural mudou aqui", mesmo antes de ler a mensagem do
Aviso. - Não depende de manter compatibilidade de desserialização entre versões do
ddf, só de recalcular o hash da estrutura atual e comparar duas strings. - É barato de calcular e comparar a cada execução, mesmo em um lote com centenas de tabelas.
Onde isso se encaixa na responsabilidade única da ACL
A responsabilidade da SobrescritaDeTabela é produzir TabelaCurada a partir de
TabelaExtraida. Ela cumpre essa responsabilidade em duas fases internas com razões de
mudança diferentes: _traduzir (mapeamento estrutural ColunaExtraida → ColunaCurada,
que muda quando a estrutura da fonte muda) e _aplicar_overrides (aplica a curadoria do
YAML, que muda quando as regras de curadoria mudam).
O cálculo do hash roda antes das duas fases, não como uma terceira, porque decide qual
caminho a chamada vai seguir. Sem override em disco, gera o skeleton. Com
override e hash batendo, aplica a curadoria existente sobre a tradução, sem reescrever
nada. Com override e hash divergente, atualiza o skeleton preservando a curadoria das
colunas que sobreviveram, e emite um Aviso explicando o que mudou.
O que o diff realmente distingue
A comparação de hash é binária: bateu ou não bateu. Quando não bate, o ddf calcula a
diferença entre os nomes de coluna do override antigo e os nomes de coluna atuais para
produzir uma mensagem específica: quais colunas foram adicionadas, quais foram removidas.
Quando os nomes de coluna são exatamente os mesmos mas o hash ainda diverge, a mensagem
cai para um caso genérico ("algo estrutural mudou, mas os nomes de coluna foram
preservados"), sem apontar qual campo específico mudou em qual coluna (um VARCHAR(50)
que virou VARCHAR(100), por exemplo, não gera uma mensagem dizendo isso).
O hash foi desenhado para responder apenas "mudou ou não mudou", e o diff de nomes de
coluna é a única granularidade adicional construída em cima dele. A limitação é real, não
uma omissão de texto. Diagnosticar exatamente qual campo mudou, hoje, é trabalho de quem
lê o diff do schema na fonte, não algo que o ddf aponta automaticamente.