Mysql
 sql >> Base de données >  >> RDS >> Mysql

Sélectionnez le CIDR qui se trouve dans la plage d'adresses IP

Stockage des adresses IP en notation quadruple pointée dans un VARCHAR n'est pas le moyen le plus optimal de les stocker, car le quadrilatère pointillé est une représentation conviviale d'un entier non signé de 32 bits qui ne se prête pas à l'indexation de la base de données. Mais parfois, c'est fondamentalement plus pratique, et à petite échelle, le fait que les requêtes nécessitent une analyse de table n'est généralement pas un problème.

Les fonctions stockées MySQL sont un bon moyen d'encapsuler une logique relativement complexe derrière une fonction simple qui peut être référencée dans une requête, ce qui peut conduire à des requêtes plus faciles à comprendre et réduire les erreurs de copier/coller.

Donc, voici une fonction stockée que j'ai écrite et appelée find_ip4_in_cidr4() . Cela fonctionne un peu de la même manière que la fonction intégrée FIND_IN_SET() -- vous lui donnez une valeur et vous lui donnez un "ensemble" (spécification CIDR) et il renvoie une valeur pour indiquer si la valeur est dans l'ensemble.

Tout d'abord, une illustration de la fonction en action :

Si l'adresse est à l'intérieur du bloc, renvoie la longueur du préfixe. Pourquoi renvoyer la longueur du préfixe ? Les entiers non nuls sont "vrais", nous pourrions donc simplement renvoyer 1 , mais si vous souhaitez trier les résultats correspondants pour trouver le plus court ou le plus long de plusieurs préfixes correspondants, vous pouvez ORDER BY la valeur de retour de la fonction.

mysql> SELECT find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24') |
+-----------------------------------------------------+
|                                                  24 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16') |
+-----------------------------------------------------+
|                                                  16 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Pas dans le bloc ? Cela renvoie 0 (faux).

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24') |
+-----------------------------------------------------+
|                                                   0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24') |
+-----------------------------------------------------+
|                                                   0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Il y a un cas particulier pour l'adresse tout à zéro, nous retournons -1 (toujours "vrai", mais préserve l'ordre de tri) :

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0');
+------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0') |
+------------------------------------------------+
|                                             -1 |
+------------------------------------------------+
1 row in set (0.00 sec)

Les arguments non-sens renvoient null :

mysql> SELECT find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24') |
+-----------------------------------------------------+
|                                                NULL |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Maintenant, le codez :

DELIMITER $$

DROP FUNCTION IF EXISTS `find_ip4_in_cidr4` $$
CREATE DEFINER=`mezzell`@`%` FUNCTION `find_ip4_in_cidr4`(
  _address VARCHAR(15), 
  _block VARCHAR(18)
) RETURNS TINYINT
DETERMINISTIC /* for a given input, this function always returns the same output */
CONTAINS SQL /* the function does not read from or write to tables */
BEGIN

-- given an IPv4 address and a cidr spec,
-- return -1 for a valid address inside 0.0.0.0/0
-- return prefix length if the address is within the block,
-- return 0 if the address is outside the block,
-- otherwise return null

DECLARE _ip_aton INT UNSIGNED DEFAULT INET_ATON(_address);
DECLARE _cidr_aton INT UNSIGNED DEFAULT INET_ATON(SUBSTRING_INDEX(_block,'/',1));
DECLARE _prefix TINYINT UNSIGNED DEFAULT SUBSTRING_INDEX(_block,'/',-1);
DECLARE _bitmask INT UNSIGNED DEFAULT (0xFFFFFFFF << (32 - _prefix)) & 0xFFFFFFFF;

RETURN CASE /* the first match, not "best" match is used in a CASE expression */
  WHEN _ip_aton IS NULL OR _cidr_aton IS NULL OR /* sanity checks */
       _prefix  IS NULL OR _bitmask IS NULL OR
       _prefix NOT BETWEEN 0 AND 32 OR
       (_prefix = 0 AND _cidr_aton != 0) THEN NULL
  WHEN _cidr_aton = 0 AND _bitmask = 0 THEN -1
  WHEN _ip_aton & _bitmask = _cidr_aton & _bitmask THEN _prefix /* here's the only actual test needed */
  ELSE 0 END;

END $$
DELIMITER ;

Un problème qui n'est pas spécifique aux fonctions stockées, mais qui s'applique plutôt à la plupart des fonctions sur la plupart des plates-formes RDBMS est que lorsqu'une colonne est utilisée comme argument d'une fonction dans WHERE , le serveur ne peut pas "regarder en arrière" via la fonction pour utiliser un index pour optimiser la requête.