Nejprve oprava, která je docela jednoduchá:Pokud chcete ukládat adresy IPv4 i IPv6, měli byste použít VARBINARY(16)
místo BINARY(16)
.
Nyní k problému:Proč to nefunguje podle očekávání s BINARY(16)
?
Uvažujme, že máme tabulku ips
pouze s jedním sloupcem ip BINARY(16) PRIMARY KEY
.Výchozí místní adresu IPv4 ukládáme pomocí
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
a najděte v databázi následující hodnotu:
0x7F000001000000000000000000000000
Jak vidíte – je to 4bajtová binární hodnota (0x7F000001
)doplněno vpravo nulami, aby se vešly do 16bajtového sloupce s pevnou délkou.
Když se jej nyní pokusíte najít pomocí
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
stane se následující:PHP odešle hodnotu 0x7F000001
jako parametr, který je poté porovnán s uloženou hodnotou 0x7F0000010000000000000000000000000000000000 .Ale protože dvě binární hodnoty různé délky nejsou nikdy stejné, podmínka WHERE vždy vrátí FALSE. Můžete to zkusit pomocí
SELECT 0x00 = 0x0000
což vrátí 0
(NEPRAVDA).
Poznámka:Chování se liší pro nebinární řetězce s pevnou délkou (CHAR(N)
).
Jako řešení bychom mohli použít explicitní casting:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
a najde řádek. Ale když se podíváme na to, co dostaneme
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
uvidíme
string(8) "7f00:1::"
Ale to není (ve skutečnosti) to, co jsme se snažili uložit. A když se nyní pokoušíme uložit 7f00:1::
,objeví se chyba duplicitního klíče , i když jsme ještě nikdy neuložili žádnou adresu IPv6.
Takže ještě jednou:Použijte VARBINARY(16)
a svůj kód můžete ponechat nedotčený. Ušetříte dokonce nějaké úložiště, pokud budete ukládat mnoho adres IPv4.