Menyimpan alamat IP dalam notasi quad bertitik dalam VARCHAR
bukan cara paling optimal untuk menyimpannya, karena dotted-quad adalah representasi ramah manusia dari bilangan bulat tidak bertanda 32 bit yang tidak cocok untuk pengindeksan basis data. Tapi kadang-kadang pada dasarnya lebih nyaman, dan pada skala kecil, fakta bahwa kueri memerlukan pemindaian tabel biasanya tidak menjadi masalah.
Fungsi Tersimpan MySQL adalah cara yang baik untuk merangkum logika yang relatif kompleks di balik fungsi sederhana yang dapat direferensikan dalam kueri, berpotensi mengarah ke kueri yang lebih mudah dipahami dan mengurangi kesalahan salin/tempel.
Jadi, inilah fungsi tersimpan yang saya tulis bernama find_ip4_in_cidr4()
. Ini bekerja agak mirip dengan fungsi bawaan FIND_IN_SET()
-- Anda memberinya nilai dan memberinya "set" (spesifikasi CIDR) dan mengembalikan nilai untuk menunjukkan apakah nilainya ada di set.
Pertama, ilustrasi fungsi yang sedang beraksi:
Jika alamat berada di dalam blok, kembalikan panjang awalan. Mengapa mengembalikan panjang awalan? Bilangan bulat bukan nol adalah "benar", jadi kita bisa mengembalikan 1
, tetapi jika Anda ingin mengurutkan hasil yang cocok untuk menemukan beberapa awalan yang cocok, Anda dapat ORDER BY
nilai kembalian fungsi.
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)
Tidak di blok? Itu mengembalikan 0 (salah).
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)
Ada kasus khusus untuk alamat semua-nol, kami mengembalikan -1 (masih "benar", tetapi mempertahankan urutan pengurutan):
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)
Argumen yang tidak masuk akal mengembalikan 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)
Sekarang, kodez:
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 ;
Masalah yang tidak spesifik untuk fungsi yang disimpan, melainkan berlaku untuk sebagian besar fungsi di sebagian besar platform RDBMS adalah ketika kolom digunakan sebagai argumen untuk fungsi di WHERE
, server tidak dapat "melihat ke belakang" melalui fungsi untuk menggunakan indeks guna mengoptimalkan kueri.