sql >> Databasteknik >  >> RDS >> Mysql

Välj CIDR som ligger inom IP-intervallet

Lagring av IP-adresser i prickad quad notation i en VARCHAR är inte det mest optimala sättet att lagra dem, eftersom dotted-quad är en människovänlig representation av ett 32-bitars osignerat heltal som inte lämpar sig för databasindexering. Men ibland är det i grunden mer bekvämt, och i liten skala är det faktum att frågor kräver en tabellskanning vanligtvis inte ett problem.

MySQL-lagrade funktioner är ett bra sätt att kapsla in relativt komplex logik bakom en enkel funktion som kan refereras till i en fråga, vilket kan leda till enklare att förstå frågor och minska kopierings-/klistrafel.

Så här är en lagrad funktion jag skrev som heter find_ip4_in_cidr4() . Det fungerar lite på samma sätt som den inbyggda funktionen FIND_IN_SET() -- du ger den ett värde och du ger den en "uppsättning" (CIDR-specifikation) och den returnerar ett värde för att indikera om värdet finns i uppsättningen.

Först en illustration av funktionen i aktion:

Om adressen är inne i blocket, returnera prefixlängden. Varför returnera prefixlängden? Heltal som inte är noll är "sanna", så vi kan bara returnera 1 , men om du vill sortera de matchande resultaten för att hitta det kortaste eller längsta av flera matchande prefix kan du ORDER BY funktionens returvärde.

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)

Inte i blocket? Det returnerar 0 (falskt).

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)

Det finns ett specialfall för adressen helt nollor, vi returnerar -1 (fortfarande "sant", men bevarar sorteringsordningen):

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)

Nonsensargument returnerar 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)

Nu, 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 ;

Ett problem som inte är specifikt för lagrade funktioner, utan snarare gäller de flesta funktioner på de flesta RDBMS-plattformar är att när en kolumn används som argument till en funktion i WHERE , servern kan inte "se bakåt" genom funktionen för att använda ett index för att optimera frågan.



  1. Utländsk nyckel som refererar till flera tabeller

  2. hur ökar man datetime-värdet med inkrementvärdet som 30 minuter i oracle?

  3. Använd tabellalias i en annan fråga för att korsa ett träd

  4. Infoga data i tabeller länkade med främmande nyckel