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.