Primeiro a correção, que é bem simples:Se você quiser armazenar ambos, endereços IPv4 e IPv6, você deve usar
VARBINARY(16) em vez de BINARY(16) . Agora para o problema:Por que não funciona como esperado com
BINARY(16) ? Considere que temos uma tabela
ips com apenas uma coluna ip BINARY(16) PRIMARY KEY .Armazenamos o endereço IPv4 local padrão com $stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
e encontre o seguinte valor no banco de dados:
0x7F000001000000000000000000000000
Como você vê - é um valor binário de 4 bytes (
0x7F000001 ) preenchido à direita com zeros para caber na coluna de comprimento fixo de 16 bytes. Quando você agora tenta encontrá-lo com
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
acontece o seguinte:PHP envia o valor
0x7F000001 como parâmetro que é comparado com o valor armazenado 0x7F000001000000000000000000000000 .Mas como dois valores binários de comprimento diferente nunca são iguais, a condição WHERE sempre retornará FALSE.Você pode tentar com SELECT 0x00 = 0x0000
que retornará
0 (FALSO). Nota:O comportamento é diferente para strings não binárias de comprimento fixo (
CHAR(N) ). Poderíamos usar a conversão explícita como solução alternativa:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
e ele encontrará a linha. Mas se olharmos para o que temos
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
Vamos ver
string(8) "7f00:1::"
Mas isso não é (realmente) o que tentamos armazenar. E quando agora tentamos armazenar
7f00:1:: , receberemos um erro de chave duplicada , embora nunca tenhamos armazenado nenhum endereço IPv6 ainda. Então, mais uma vez:Use
VARBINARY(16) , e você pode manter seu código intacto. Você ainda economizará algum armazenamento, se armazenar muitos endereços IPv4.