Speichern von IP-Adressen in gepunkteter Quad-Notation in einem VARCHAR
ist nicht die optimale Art, sie zu speichern, da dotted-quad eine benutzerfreundliche Darstellung einer 32-Bit-Ganzzahl ohne Vorzeichen ist, die sich nicht für die Datenbankindizierung eignet. Aber manchmal ist es wesentlich bequemer, und im kleinen Maßstab ist die Tatsache, dass Abfragen einen Tabellenscan erfordern, normalerweise kein Problem.
Gespeicherte MySQL-Funktionen sind eine gute Möglichkeit, relativ komplexe Logik hinter einer einfachen Funktion zu kapseln, auf die in einer Abfrage verwiesen werden kann, was möglicherweise zu leichter verständlichen Abfragen führt und Fehler beim Kopieren/Einfügen reduziert.
Also, hier ist eine gespeicherte Funktion namens find_ip4_in_cidr4()
, die ich geschrieben habe . Sie funktioniert ähnlich wie die eingebaute Funktion FIND_IN_SET()
-- Sie geben ihm einen Wert und Sie geben ihm ein "Set" (CIDR-Spezifikation) und es gibt einen Wert zurück, um anzugeben, ob der Wert in dem Set enthalten ist.
Zuerst eine Illustration der Funktion in Aktion:
Wenn sich die Adresse innerhalb des Blocks befindet, geben Sie die Präfixlänge zurück. Warum die Präfixlänge zurückgeben? Ganzzahlen ungleich Null sind „wahr“, also könnten wir einfach 1
zurückgeben , aber wenn Sie die übereinstimmenden Ergebnisse sortieren möchten, um das kürzeste oder längste von mehreren übereinstimmenden Präfixen zu finden, können Sie ORDER BY
verwenden der Rückgabewert der Funktion.
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)
Nicht im Block? Das gibt 0 (falsch) zurück.
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)
Es gibt einen Sonderfall für die Nur-Nullen-Adresse, wir geben -1 zurück (immer noch "wahr", aber behält die Sortierreihenfolge bei):
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)
Unsinnige Argumente geben null zurück:
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)
Nun, der 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 ;
Ein Problem, das nicht spezifisch für gespeicherte Funktionen ist, sondern eher für die meisten Funktionen auf den meisten RDBMS-Plattformen gilt, ist, wenn eine Spalte als Argument für eine Funktion in WHERE
verwendet wird , kann der Server nicht durch die Funktion "rückwärts schauen", um einen Index zum Optimieren der Abfrage zu verwenden.