Memorizzazione degli indirizzi IP in notazione quadrupla puntata in un VARCHAR
non è il modo più ottimale per archiviarli, poiché il quadruplo puntinato è una rappresentazione umana di un intero senza segno a 32 bit che non si presta all'indicizzazione del database. Ma a volte è fondamentalmente più conveniente e, su piccola scala, il fatto che le query richiedano un'analisi della tabella di solito non è un problema.
Le MySQL Stored Functions sono un buon modo per incapsulare una logica relativamente complessa dietro una semplice funzione a cui è possibile fare riferimento in una query, portando potenzialmente a query di più facile comprensione e riducendo gli errori di copia/incolla.
Quindi, ecco una funzione memorizzata che ho scritto chiamata find_ip4_in_cidr4()
. Funziona in modo simile alla funzione integrata FIND_IN_SET()
-- gli dai un valore e gli dai un "set" (specifica CIDR) e restituisce un valore per indicare se il valore è nel set.
Innanzitutto, un'illustrazione della funzione in azione:
Se l'indirizzo è all'interno del blocco, restituire la lunghezza del prefisso. Perché restituire la lunghezza del prefisso? Gli interi diversi da zero sono "veri", quindi potremmo semplicemente restituire 1
, ma se vuoi ordinare i risultati corrispondenti per trovare il più breve o il più lungo di più prefissi corrispondenti, puoi ORDER BY
il valore di ritorno della funzione.
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)
Non nel blocco? Ciò restituisce 0 (falso).
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)
C'è un caso speciale per l'indirizzo tutto zero, restituiamo -1 (ancora "vero", ma conserva l'ordinamento):
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)
Gli argomenti senza senso restituiscono 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)
Ora, il codice:
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 problema che non è specifico delle funzioni memorizzate, ma si applica piuttosto alla maggior parte delle funzioni sulla maggior parte delle piattaforme RDBMS è che quando una colonna viene utilizzata come argomento per una funzione in WHERE
, il server non può "guardare indietro" tramite la funzione per utilizzare un indice per ottimizzare la query.