Først rettelsen, som er ret enkel:Hvis du vil gemme både IPv4- og IPv6-adresser, skal du bruge VARBINARY(16)
i stedet for BINÆR(16)
.
Nu til problemet:Hvorfor virker det ikke som forventet med BINARY(16)
?
Overvej, at vi har en tabel ips
med kun én kolonne ip BINÆR(16) PRIMÆR NØGLE
.Vi gemmer den lokale standard IPv4-adresse med
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
og find følgende værdi i databasen:
0x7F000001000000000000000000000000
Som du kan se - det er en 4 byte binær værdi (0x7F000001
)højrepolstret med nuller for at passe til kolonnen med 16 bytes fast længde.
Når du nu prøver at finde den med
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
følgende sker:PHP sender værdien 0x7F000001
som parameter, som derefter sammenlignes med den gemte værdi 0x7F000001000000000000000000000000000000000000 .Men da to binære værdier af forskellig længde aldrig er ens, vil WHERE-betingelsen altid returnere FALSE. Du kan prøve det med
SELECT 0x00 = 0x0000
hvilket vil returnere 0
(FALSK).
Bemærk:Opførselen er anderledes for ikke-binære strenge med fast længde (CHAR(N)
).
Vi kunne bruge eksplicit casting som en løsning:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
og den vil finde rækken. Men hvis vi ser på, hvad vi får
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
vi får se
string(8) "7f00:1::"
Men det er (virkelig) ikke det, vi har forsøgt at gemme. Og når vi nu forsøger at gemme 7f00:1::
, vil vi få en dublet nøglefejl , selvom vi aldrig har gemt nogen IPv6-adresse endnu.
Så endnu en gang:Brug VARBINARY(16)
, og du kan beholde din kode urørt. Du sparer endda noget lager, hvis du gemmer mange IPv4-adresser.