sql >> Base de Datos >  >> RDS >> Mysql

No se puede seleccionar donde ip=inet_pton($ip)

Primero, la solución, que es bastante simple:si desea almacenar direcciones IPv4 e IPv6, debe usar VARBINARY(16) en lugar de BINARY(16) .

Ahora al problema:¿Por qué no funciona como se esperaba con BINARY(16) ?

Considere que tenemos una tabla ips con una sola columna ip BINARY(16) PRIMARY KEY .Almacenamos la dirección IPv4 local predeterminada con

$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);

y busque el siguiente valor en la base de datos:

0x7F000001000000000000000000000000

Como puede ver, es un valor binario de 4 bytes (0x7F000001 )rellenado a la derecha con ceros para ajustarse a la columna de longitud fija de 16 bytes.

Cuando ahora intentas encontrarlo con

$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);

sucede lo siguiente:PHP envía el valor 0x7F000001 como parámetro que luego se compara con el valor almacenado 0x7F000001000000000000000000000000 .Pero dado que dos valores binarios de diferente longitud nunca son iguales, la condición WHERE siempre devolverá FALSO. Puede intentarlo con

SELECT 0x00 = 0x0000

que devolverá 0 (FALSO).

Nota:el comportamiento es diferente para cadenas no binarias de longitud fija (CHAR(N) ).

Podríamos usar la conversión explícita como solución alternativa:

$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);

y encontrará la fila. Pero si miramos lo que obtenemos

var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));

ya veremos

string(8) "7f00:1::"

Pero eso no es (realmente) lo que hemos tratado de almacenar. Y cuando ahora intentamos almacenar 7f00:1:: ,obtendremos un error de clave duplicada ,aunque nunca hemos almacenado ninguna dirección IPv6 todavía.

Entonces, una vez más:usa VARBINARY(16) , y puede mantener su código intacto. Incluso ahorrará algo de almacenamiento, si almacena muchas direcciones IPv4.